Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/little-foxes-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@highlight-run/sourcemap-uploader': patch
---

support next.js route groups by removing frontend groups from paths
6 changes: 5 additions & 1 deletion backend/public-graph/graph/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2189,7 +2189,11 @@ func (r *Resolver) ProcessBackendPayloadImpl(ctx context.Context, sessionSecureI
var stackFrameInput []*publicModel.StackFrameInput

if err := json.Unmarshal([]byte(v.StackTrace), &stackFrameInput); err == nil {
mapped, structured, err := r.getMappedStackTraceString(ctx, stackFrameInput, projectID, errorToInsert, pointy.String(fmt.Sprintf("%s-%s", v.Service.Name, v.Service.Version)))
var version *string
if v.Service.Name != "" && v.Service.Version != "" {
version = pointy.String(fmt.Sprintf("%s-%s", v.Service.Name, v.Service.Version))
}
mapped, structured, err := r.getMappedStackTraceString(ctx, stackFrameInput, projectID, errorToInsert, version)
if err != nil {
log.WithContext(ctx).Errorf("Error generating mapped stack trace: %v", v.StackTrace)
} else if mapped != nil && *mapped != "null" {
Expand Down
12 changes: 10 additions & 2 deletions backend/stacktraces/enhancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ func mapFileForJS(jsFile string) string {
return fmt.Sprintf("%s.map", stripStackTraceQueryString(jsFile))
}

var NextNodeServerlessRegex = regexp.MustCompile(`/var/task/.+/\.next/(.+)`)

func processStackFrame(ctx context.Context, projectId int, version *string, stackTrace publicModel.StackFrameInput, storageClient storage.Client) (*privateModel.ErrorTrace, error, privateModel.SourceMappingError) {
stackTraceFileURL := *stackTrace.FileName
stackTraceLineNumber := *stackTrace.LineNumber
Expand Down Expand Up @@ -372,8 +374,14 @@ func processStackFrame(ctx context.Context, projectId int, version *string, stac
return nil, err, stackTraceError
}
stackTraceFilePath := u.Path
if len(stackTraceFilePath) > 0 && stackTraceFilePath[0:1] == "/" {
stackTraceFilePath = stackTraceFilePath[1:]
if len(stackTraceFilePath) > 0 {
if matches := NextNodeServerlessRegex.FindStringSubmatch(stackTraceFilePath); matches != nil {
stackTraceFilePath = "_next/" + matches[1]
stackTraceFileURL = stackTraceFilePath
stackFileNameIndex = strings.Index(stackTraceFileURL, path.Base(stackTraceFileURL))
} else if stackTraceFilePath[0:1] == "/" {
stackTraceFilePath = stackTraceFilePath[1:]
}
}

stackTraceFileURL = stripStackTraceQueryString(stackTraceFileURL)
Expand Down
23 changes: 23 additions & 0 deletions backend/stacktraces/enhancer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package stacktraces

import (
"context"
"encoding/json"
"github.com/openlyinc/pointy"
"github.com/stretchr/testify/assert"
"testing"
Expand Down Expand Up @@ -274,6 +275,28 @@ func TestEnhanceStackTrace(t *testing.T) {
}
}

func TestEnhanceBackendNextServerlessTrace(t *testing.T) {
ctx := context.TODO()

client, err := storage.NewFSClient(ctx, "http://localhost:8082/public", "./test-files")
if err != nil {
t.Fatalf("error creating storage client: %v", err)
}

fetch = NetworkFetcher{}

var stackFrameInput []*publicModelInput.StackFrameInput
err = json.Unmarshal([]byte(`[{"fileName":"/var/task/apps/magicsky/.next/server/app/(route-group-test)/[slug]/page-router-edge-test.js","lineNumber":1,"functionName":"s","columnNumber":3344,"error":"Error: 🎉 SSR Error with use-server: src/app-router/ssr/page.tsx"}]`), &stackFrameInput)
assert.NoError(t, err)

mappedStackTrace, err := EnhanceStackTrace(ctx, stackFrameInput, 29954, nil, client)
if err != nil {
t.Fatal(e.Wrap(err, "error enhancing source map"))
}
assert.Equal(t, 1, len(mappedStackTrace))
assert.Equal(t, " console.log(`got fetch cache entry for ${key}, duration: ${Date.now() - start}ms, size: ${Object.keys(cached).length}, cache-state: ${cacheState} tags: ${tags == null ? void 0 : tags.join(\",\")} softTags: ${softTags == null ? void 0 : softTags.join(\",\")}`);\n", *mappedStackTrace[0].LineContent)
}

func TestGetURLSourcemap(t *testing.T) {
ctx := context.Background()
fsClient, err := storage.NewFSClient(ctx, "http://localhost:8082/public", "")
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions e2e/nextjs/src/app/(route-group-test)/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Link from 'next/link'

export default function AnotherPage({ params }: { params: { slug: string } }) {
return (
<div>
<h1>This is a route group slug page</h1>
<p>Slug: {params?.slug}</p>
<Link href="/">Go To Your Home</Link>
<button
onClick={() => {
throw new Error('route group error')
}}
></button>
</div>
)
}
21 changes: 18 additions & 3 deletions sourcemap-uploader/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,14 @@ export const uploadSourcemaps = async ({
const uploadUrls = urlRes.data.get_source_map_upload_urls;

await Promise.all(
fileList.map(({ path }, idx) => uploadFile(path, uploadUrls[idx]))
fileList.map(({ path, name }, idx) =>
uploadFile(path, uploadUrls[idx], name)
)
);
};

const NextRouteGroupPattern = new RegExp(/(\(.+?\))\//gm);

async function getAllSourceMapFiles(
paths: string[],
{ allowNoop }: { allowNoop?: boolean }
Expand Down Expand Up @@ -170,6 +174,17 @@ async function getAllSourceMapFiles(
path: join(realPath, file),
name: file,
});
const routeGroupRemovedPath = file.replaceAll(
new RegExp(/(\(.+?\))\//gm),
""
);
if (file !== routeGroupRemovedPath) {
// also upload the file to a path without the route group for frontend errors
map.push({
path: join(realPath, file),
name: routeGroupRemovedPath,
});
}
}
})
);
Expand All @@ -190,8 +205,8 @@ function getS3Key(
return `${organizationId}/${version}/${basePath}${fileName}`;
}

async function uploadFile(filePath: string, uploadUrl: string) {
async function uploadFile(filePath: string, uploadUrl: string, name: string) {
const fileContent = readFileSync(filePath);
await fetch(uploadUrl, { method: "put", body: fileContent });
console.log(`Uploaded ${filePath}`);
console.log(`Uploaded ${filePath} to ${name}`);
}