Skip to content

Commit 39ded55

Browse files
committed
Resolve isolated playground dependencies to installed package targets.
1 parent 92ad9b6 commit 39ded55

3 files changed

Lines changed: 84 additions & 9 deletions

File tree

‎packages/void/test/unit/integration-helpers.test.ts‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import {
55
mkdirSync,
66
mkdtempSync,
77
readFileSync,
8+
readlinkSync,
9+
realpathSync,
810
rmSync,
11+
symlinkSync,
912
writeFileSync,
1013
} from 'node:fs';
1114
import { tmpdir } from 'node:os';
12-
import { join } from 'node:path';
15+
import { isAbsolute, join, relative } from 'node:path';
1316
import { describe, expect, it } from 'vite-plus/test';
1417
import {
1518
createIsolatedPlayground,
@@ -100,3 +103,48 @@ it('clears the shutdown deadline after a real child exits', () => {
100103
expect(result.status, result.stderr).toBe(0);
101104
expect(result.stdout.trim()).toBe('[]');
102105
}, 20_000);
106+
107+
it('resolves installed dependency links absolutely at the isolated fixture depth', () => {
108+
const workspace = mkdtempSync(join(tmpdir(), 'void-fixture-dependencies-'));
109+
const source = join(workspace, 'playground', 'app');
110+
const modules = join(source, 'node_modules');
111+
mkdirSync(join(modules, '@fixture'), { recursive: true });
112+
writeFileSync(join(source, 'package.json'), JSON.stringify({ name: 'dependency-app' }));
113+
writeFileSync(
114+
join(source, 'entry.mjs'),
115+
'import plain from "plain"; import scoped from "@fixture/scoped"; console.log(JSON.stringify([plain, scoped]));',
116+
);
117+
for (const name of ['plain', '@fixture/scoped']) {
118+
const installed = join(workspace, 'packages', name);
119+
mkdirSync(installed, { recursive: true });
120+
writeFileSync(
121+
join(installed, 'package.json'),
122+
JSON.stringify({ name, type: 'module', exports: './index.mjs' }),
123+
);
124+
writeFileSync(join(installed, 'index.mjs'), `export default ${JSON.stringify(name)};`);
125+
const link = join(modules, name);
126+
symlinkSync(
127+
relative(join(link, '..'), installed),
128+
link,
129+
process.platform === 'win32' ? 'junction' : 'dir',
130+
);
131+
}
132+
const fixture = createIsolatedPlayground(workspace, 'playground/app');
133+
try {
134+
for (const name of ['plain', '@fixture/scoped']) {
135+
const link = join(fixture.root, 'node_modules', name);
136+
expect(isAbsolute(readlinkSync(link))).toBe(true);
137+
expect(realpathSync(link)).toBe(realpathSync(join(workspace, 'packages', name)));
138+
}
139+
const result = spawnSync(process.execPath, ['entry.mjs'], {
140+
cwd: fixture.root,
141+
encoding: 'utf8',
142+
timeout: 10_000,
143+
});
144+
expect(result.status, result.stderr).toBe(0);
145+
expect(JSON.parse(result.stdout)).toEqual(['plain', '@fixture/scoped']);
146+
} finally {
147+
fixture.cleanup();
148+
rmSync(workspace, { recursive: true, force: true });
149+
}
150+
});

‎packages/void/test/unit/playground-snapshots.test.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ it('clones stable source snapshots while resolving dependencies from the actual
1717
const source = join(root, 'playground/react-pages');
1818
const snapshot = join(root, 'snapshots/playground/react-pages');
1919
mkdirSync(join(source, 'node_modules'), { recursive: true });
20+
mkdirSync(join(source, 'node_modules', 'fixture-dependency'));
21+
writeFileSync(join(source, 'node_modules', 'fixture-dependency', 'index.js'), 'installed source');
2022
mkdirSync(snapshot, { recursive: true });
2123
for (const path of [source, snapshot]) {
2224
writeFileSync(join(path, 'package.json'), JSON.stringify({ name: 'test-app' }));
@@ -27,8 +29,8 @@ it('clones stable source snapshots while resolving dependencies from the actual
2729

2830
const fixture = createIsolatedPlayground(root, 'playground/react-pages');
2931
expect(readFileSync(join(fixture.root, 'page.ts'), 'utf8')).toBe('original source');
30-
expect(realpathSync(join(fixture.root, 'node_modules'))).toBe(
31-
realpathSync(join(source, 'node_modules')),
32+
expect(realpathSync(join(fixture.root, 'node_modules', 'fixture-dependency'))).toBe(
33+
realpathSync(join(source, 'node_modules', 'fixture-dependency')),
3234
);
3335
expect(readFileSync(join(source, 'page.ts'), 'utf8')).toBe('temporary HMR mutation');
3436
fixture.cleanup();

‎test/integration-helpers.ts‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
mkdirSync,
77
mkdtempSync,
88
readFileSync,
9+
readdirSync,
10+
realpathSync,
911
rmSync,
1012
symlinkSync,
1113
writeFileSync,
@@ -80,18 +82,41 @@ export function createIsolatedPlayground(
8082
filter(path) {
8183
// `cpSync` reports native backslash paths on Windows, while these workspace paths are
8284
// normalized to forward slashes. Normalize before using the POSIX path helpers or the
83-
// top-level `node_modules` junction is copied and the replacement junction already exists.
85+
// top-level `node_modules` directory is copied into the isolated dependency tree.
8486
return shouldCopyPlaygroundPath(copySource, path);
8587
},
8688
});
8789

8890
const sourceNodeModules = join(sourceRoot, 'node_modules');
8991
if (existsSync(sourceNodeModules)) {
90-
symlinkSync(
91-
sourceNodeModules,
92-
join(root, 'node_modules'),
93-
process.platform === 'win32' ? 'junction' : 'dir',
94-
);
92+
const fixtureNodeModules = join(root, 'node_modules');
93+
mkdirSync(fixtureNodeModules);
94+
const linkDependency = (source: string, destination: string) =>
95+
symlinkSync(
96+
realpathSync(source),
97+
destination,
98+
process.platform === 'win32' ? 'junction' : 'dir',
99+
);
100+
// Linking the whole directory leaves pnpm's relative package links behind
101+
// a Windows junction at a different depth. Pin each package's real target.
102+
for (const entry of readdirSync(sourceNodeModules, { withFileTypes: true })) {
103+
if (!entry.isDirectory() && !entry.isSymbolicLink()) {
104+
continue;
105+
}
106+
if (entry.name.startsWith('.') && entry.name !== '.bin') {
107+
continue;
108+
}
109+
const source = join(sourceNodeModules, entry.name);
110+
const destination = join(fixtureNodeModules, entry.name);
111+
if (entry.name.startsWith('@')) {
112+
mkdirSync(destination);
113+
for (const name of readdirSync(source)) {
114+
linkDependency(join(source, name), join(destination, name));
115+
}
116+
} else {
117+
linkDependency(source, destination);
118+
}
119+
}
95120
}
96121

97122
// Avoid duplicate workspace package names when multiple copies exist concurrently.

0 commit comments

Comments
 (0)