Repository: codesandbox/codesandbox-client (language: javascript)

Issue:
Sandboxes from CI are not fetching dependencies
# 🐛 bug report

## Preflight Checklist

<!-- Please ensure you've completed the following steps by replacing [ ] with [x]-->

- [x] I have read the [Contributing Guidelines][contributing] for this project.
- [x] I agree to follow the [Code of Conduct][code_of_conduct] that this project
      adheres to.
- [x] I have searched the issue tracker for an issue that matches the one I want
      to file, without success.

## Description of the problem

I have been receiving this error when submitting PRs to my repo and visiting the sandbox url generated from sandbox ci:

> Could not fetch dependencies, please try again in a couple seconds: Cannot read property 'content' of undefined

If I go to import the same sandbox as a new URL everything works as expected: https://codesandbox.io/s/github/plouc/nivo/tree/master/examples/codesandbox

Here is the link to one of the affected CI runs: https://ci.codesandbox.io/status/plouc/nivo/pr/1538/builds/132648

`nivo` sandbox is the one erroring. The `nivo-website` sandbox still works correctly.

## How has this issue affected you? What are you trying to accomplish?

Trying to view the sandbox after CI runs.

### To Reproduce

Submit a PR to the plouc/nivo repo.

<!--
Your best chance of getting this bug looked at quickly is to provide an example.
-->

### Link to sandbox: [link](https://codesandbox.io/s/nivo-ck1wp) (optional)

### Your Environment

| Software         | Name/Version |
| ---------------- | ------------ |
| Сodesandbox      | 72d987691    |
| Browser          | Chrome 90    |
| Operating System | macOS 10.15  |

<!-- prettier-ignore-start -->
[code_of_conduct]: https://github.com/codesandbox/codesandbox-client/blob/master/CODE_OF_CONDUCT.md
[contributing]: https://github.com/codesandbox/codesandbox-client/blob/master/CONTRIBUTING.md
<!-- prettier-ignore-end -->



Discussion / hints:
Hey @wyze ,

Thanks for your report!

I can reproduce this in https://codesandbox.io/s/nivo-ck1wp, we'll take a look at it and get back to you.

Fix patch:
diff --git a/packages/sandpack-core/src/npm/dynamic/fetch-protocols/csb.ts b/packages/sandpack-core/src/npm/dynamic/fetch-protocols/csb.ts
index 8e535ddde71..9301da08fae 100644
--- a/packages/sandpack-core/src/npm/dynamic/fetch-protocols/csb.ts
+++ b/packages/sandpack-core/src/npm/dynamic/fetch-protocols/csb.ts
@@ -1,42 +1,20 @@
 import { FetchProtocol, Meta } from '../fetch-npm-module';
-import { fetchWithRetries } from './utils';
+import { TarStore } from './utils/tar-store';
 
-type UnpkgMetaFiles = {
-  path: string;
-  type: 'file' | 'directory';
-  files?: UnpkgMetaFiles[];
-};
-
-function normalize(files: UnpkgMetaFiles[], fileObject: Meta = {}) {
-  for (let i = 0; i < files.length; i += 1) {
-    const { files: childFiles, type, path } = files[i];
-    if (type === 'file') {
-      const absolutePath = path;
-      fileObject[absolutePath] = true; // eslint-disable-line no-param-reassign
-    }
-
-    if (childFiles) {
-      normalize(childFiles, fileObject);
-    }
-  }
-
-  return fileObject;
+function getTarballUrl(url: string) {
+  return url.replace(/\/_pkg.tgz$/, '');
 }
 
 export class CsbFetcher implements FetchProtocol {
-  async file(name: string, version: string, path: string): Promise<string> {
-    const url = `${version.replace(/\/_pkg.tgz$/, '')}${path}`;
-    const result = await fetchWithRetries(url).then(x => x.text());
+  private tarStore = new TarStore();
 
-    return result;
+  async file(name: string, version: string, path: string): Promise<string> {
+    const url = getTarballUrl(version);
+    return this.tarStore.file(name, url, path);
   }
 
   async meta(name: string, version: string): Promise<Meta> {
-    const url = `${version.replace(/\/\.pkg.tgz$/, '')}/_csb-meta.json`;
-    const result: UnpkgMetaFiles = await fetchWithRetries(url).then(x =>
-      x.json()
-    );
-
-    return normalize(result.files!, {});
+    const url = getTarballUrl(version);
+    return this.tarStore.meta(name, url);
   }
 }
diff --git a/packages/sandpack-core/src/npm/dynamic/resolve-dependency.ts b/packages/sandpack-core/src/npm/dynamic/resolve-dependency.ts
index 3a46f3cbe0b..05ab9436bc2 100644
--- a/packages/sandpack-core/src/npm/dynamic/resolve-dependency.ts
+++ b/packages/sandpack-core/src/npm/dynamic/resolve-dependency.ts
@@ -5,7 +5,7 @@ import { ILambdaResponse } from '../merge-dependency';
 import { downloadDependency } from './fetch-npm-module';
 import { IParsedResolution } from './resolutions';
 
-async function getPackageJSON(dep: string, version: string) {
+export async function getPackageJSON(dep: string, version: string) {
   const m = await downloadDependency(dep, version, '/package.json');
   return m.code;
 }
@@ -143,5 +143,17 @@ export async function resolveDependencyInfo(
     },
   };
 
+  await Promise.all(
+    Object.keys(response.dependencyDependencies).map(async packageName => {
+      const pkgJson = await getPackageJSON(
+        packageName,
+        response.dependencyDependencies[packageName].resolved
+      );
+      response.contents[`/node_modules/${packageName}/package.json`] = {
+        content: pkgJson,
+      };
+    })
+  );
+
   return response;
 }
diff --git a/packages/sandpack-core/src/npm/merge-dependency.ts b/packages/sandpack-core/src/npm/merge-dependency.ts
index 94633ae4651..31cbd02bbff 100644
--- a/packages/sandpack-core/src/npm/merge-dependency.ts
+++ b/packages/sandpack-core/src/npm/merge-dependency.ts
@@ -98,6 +98,10 @@ function replaceDependencyInfo(
   // Don't change anything if the module is already part of a sub-folder
   // in this case pkg.json won't contain the requested version...
   const pkgJSONPath = `/node_modules/${depDepName}/package.json`;
+  if (!res.contents[pkgJSONPath]) {
+    throw new Error(`Dependency ${depDepName} not found`);
+  }
+
   const parsedPkgJson = JSON.parse(res.contents[pkgJSONPath].content);
   if (parsedPkgJson.version !== newDepDep.resolved) {
     return false;
@@ -142,6 +146,10 @@ function replaceDependencyInfo(
 }
 
 const intersects = (v1: string, v2: string) => {
+  if (v1 === v2) {
+    return true;
+  }
+
   try {
     return semver.intersects(v1, v2);
   } catch (e) {
@@ -195,7 +203,6 @@ export function mergeDependencies(responses: ILambdaResponse[]) {
         // TODO: Also remove contents for conflicts in transient dependencies?
       } else if (response.dependencyDependencies[depDepName]) {
         const exDepDep = response.dependencyDependencies[depDepName];
-
         // Determine which version is newer, needed for some checks later.
         const [newerVersionDepDep, olderVersionDepDep] = semver.gt(
           newDepDep.resolved,


Test patch: