mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
d504f022b0
* fix(assets): default assetsDir to _next/static (Next.js parity) Closes #1337. Supersedes #1383. vinext's default `assetsDir` was Vite's historical `assets/`, so URL emission (`/_next/static/...` via `resolveAssetUrlPrefix("")`) and on-disk layout (`dist/client/assets/...`) disagreed in the empty-`assetPrefix` case. The `build.assetsDir` and `experimental.renderBuiltUrl` Vite overrides only applied when `assetPrefix` was configured, leaving the no-prefix branch serving from a different path than the URL contract Next.js's client runtime and test harness assert against. This commit flips the default to Next.js's canonical layout: - `resolveAssetsDir("")` returns `_next/static` (was `assets`) - `build.assetsDir` is now set unconditionally from `resolveAssetsDir` - Vite's default `base + assetsDir` composition produces correct URLs in the no-prefix case; `renderBuiltUrl` stays gated on `assetPrefix` because it's only needed for the configured cases - Drops the legacy `/assets/` branches in prod-server (3 sites) and static-file-cache (hard cutover — see PR description for rationale) - Updates `_headers` generation, precompress default, fonts plugin `DEFAULT_ASSETS_DIR` to match the new default With the layout aligned, invalid `_next/static/*` requests naturally return plain-text `404 + "Not Found"` from the static-file layer instead of falling through to the page renderer (which would produce an HTML 404 with bootstrap scripts and CSS). This replaces #1383's parity short-circuit with the natural code path: - Node prod-server (App + Pages branches): missing asset under `resolveAppRouterAssetPath` returns `text/plain; charset=utf-8` 404 - Cloudflare worker entry: `isNextStaticPath` recognises asset-shape after ASSETS-binding misses; returns `notFoundStaticAssetResponse` Mirrors Next.js: packages/next/src/server/lib/router-server.ts. Adds `tests/invalid-static-asset-404.test.ts` (3 App Router + 3 Pages Router cases — no prefix / basePath / assetPrefix) ported from Next.js e2e suites. Updates 11 source files + 12 test files; 914+ tests pass in the touched suites (asset-prefix, app-router, pages-router, deploy, font-google, static-file-cache, serve-static, precompress, features, routing, isr-cache, build-optimization, app-rsc/ssr, standalone, middleware, shims). Ported from Next.js: - test/e2e/invalid-static-asset-404-app/*.test.ts - test/e2e/invalid-static-asset-404-pages/*.test.ts * fix(e2e): update cloudflare-pages-router hydration spec for _next/static * PR #1411 approved. Clean fix, 2 nits. Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> * refactor(assets): derive isHashed checks from ASSET_PREFIX_URL_DIR Replace hardcoded '_next/static' string literals in runtime code paths with the ASSET_PREFIX_URL_DIR constant so the asset URL convention has a single source of truth. - server/prod-server.ts isHashed: pathname.includes(`/${ASSET_PREFIX_URL_DIR}/`) - server/prod-server.ts public-dir guard: startsWith(`/${ASSET_PREFIX_URL_DIR}/`) - server/static-file-cache.ts isHashed: both startsWith + includes derived - plugins/fonts.ts DEFAULT_ASSETS_DIR = ASSET_PREFIX_URL_DIR Behavioural no-op — the constant is '_next/static' so the resolved strings are identical. Centralises the URL contract so future changes to ASSET_PREFIX_URL_DIR (or a refactor that derives it from build config) flow through to every consumer. Doc-comment references to '_next/static' left in place — they describe the canonical value for human readers and are not code paths. --------- Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com> Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>
511 lines
16 KiB
TypeScript
511 lines
16 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { emitStandaloneOutput } from "../packages/vinext/src/build/standalone.js";
|
|
|
|
let tmpDir: string;
|
|
|
|
function createTmpDir(): string {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), "vinext-standalone-test-"));
|
|
}
|
|
|
|
function writeFile(root: string, relativePath: string, content: string): void {
|
|
const target = path.join(root, relativePath);
|
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
fs.writeFileSync(target, content, "utf-8");
|
|
}
|
|
|
|
function writePackage(
|
|
root: string,
|
|
packageName: string,
|
|
dependencies: Record<string, string> = {},
|
|
options: { exports?: Record<string, string> } = {},
|
|
): void {
|
|
const packageRoot = path.join(root, "node_modules", packageName);
|
|
fs.mkdirSync(packageRoot, { recursive: true });
|
|
const pkgJson: Record<string, unknown> = {
|
|
name: packageName,
|
|
version: "1.0.0",
|
|
main: "index.js",
|
|
dependencies,
|
|
};
|
|
if (options.exports) {
|
|
pkgJson.exports = options.exports;
|
|
}
|
|
fs.writeFileSync(
|
|
path.join(packageRoot, "package.json"),
|
|
JSON.stringify(pkgJson, null, 2),
|
|
"utf-8",
|
|
);
|
|
fs.writeFileSync(path.join(packageRoot, "index.js"), "module.exports = {};\n", "utf-8");
|
|
}
|
|
|
|
beforeEach(() => {
|
|
tmpDir = createTmpDir();
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe("emitStandaloneOutput", () => {
|
|
it("copies packages listed in vinext-externals.json and their transitive deps", () => {
|
|
const appRoot = path.join(tmpDir, "app");
|
|
fs.mkdirSync(appRoot, { recursive: true });
|
|
|
|
writeFile(
|
|
appRoot,
|
|
"package.json",
|
|
JSON.stringify(
|
|
{
|
|
name: "app",
|
|
dependencies: {
|
|
// dep-a is in package.json#dependencies but NOT in the externals manifest.
|
|
// The standalone builder should NOT copy it — only manifest entries matter.
|
|
"dep-a": "1.0.0",
|
|
react: "1.0.0",
|
|
vinext: "1.0.0",
|
|
},
|
|
devDependencies: {
|
|
typescript: "5.0.0",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
|
|
writeFile(appRoot, "dist/client/_next/static/main.js", "console.log('client');\n");
|
|
writeFile(appRoot, "dist/server/entry.js", 'console.log("server");\n');
|
|
// The externals manifest is written by vinext:server-externals-manifest at build time.
|
|
// It contains only the packages the server bundle actually imports at runtime.
|
|
writeFile(
|
|
appRoot,
|
|
"dist/server/vinext-externals.json",
|
|
JSON.stringify(["react", "react-server-dom-webpack"]),
|
|
);
|
|
writeFile(appRoot, "public/robots.txt", "User-agent: *\n");
|
|
|
|
writePackage(appRoot, "dep-a", { "dep-b": "1.0.0" });
|
|
writePackage(appRoot, "dep-b");
|
|
writePackage(appRoot, "react");
|
|
writePackage(appRoot, "react-server-dom-webpack");
|
|
|
|
const fakeVinextRoot = path.join(tmpDir, "fake-vinext");
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"package.json",
|
|
JSON.stringify(
|
|
{
|
|
name: "vinext",
|
|
version: "0.0.0-test",
|
|
type: "module",
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"dist/server/prod-server.js",
|
|
"export async function startProdServer() {}\n",
|
|
);
|
|
|
|
const result = emitStandaloneOutput({
|
|
root: appRoot,
|
|
outDir: path.join(appRoot, "dist"),
|
|
vinextPackageRoot: fakeVinextRoot,
|
|
});
|
|
|
|
// Packages from the externals manifest are copied.
|
|
expect(result.copiedPackages).toContain("react");
|
|
expect(result.copiedPackages).toContain("react-server-dom-webpack");
|
|
expect(result.copiedPackages).toContain("vinext");
|
|
|
|
// dep-a is in package.json#dependencies but NOT in the manifest — must NOT be copied.
|
|
expect(result.copiedPackages).not.toContain("dep-a");
|
|
expect(result.copiedPackages).not.toContain("dep-b");
|
|
// devDependencies must never be copied.
|
|
expect(result.copiedPackages).not.toContain("typescript");
|
|
|
|
expect(fs.existsSync(path.join(appRoot, "dist/standalone/server.js"))).toBe(true);
|
|
expect(fs.readFileSync(path.join(appRoot, "dist/standalone/server.js"), "utf-8")).toContain(
|
|
"startProdServer",
|
|
);
|
|
const standalonePkg = JSON.parse(
|
|
fs.readFileSync(path.join(appRoot, "dist/standalone/package.json"), "utf-8"),
|
|
) as { type: string };
|
|
expect(standalonePkg.type).toBe("module");
|
|
|
|
expect(
|
|
fs.existsSync(path.join(appRoot, "dist/standalone/dist/client/_next/static/main.js")),
|
|
).toBe(true);
|
|
expect(fs.existsSync(path.join(appRoot, "dist/standalone/dist/server/entry.js"))).toBe(true);
|
|
expect(fs.existsSync(path.join(appRoot, "dist/standalone/public/robots.txt"))).toBe(true);
|
|
|
|
expect(
|
|
fs.existsSync(path.join(appRoot, "dist/standalone/node_modules/react/package.json")),
|
|
).toBe(true);
|
|
expect(
|
|
fs.existsSync(
|
|
path.join(appRoot, "dist/standalone/node_modules/react-server-dom-webpack/package.json"),
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
fs.existsSync(path.join(appRoot, "dist/standalone/node_modules/dep-a/package.json")),
|
|
).toBe(false);
|
|
expect(
|
|
fs.existsSync(path.join(appRoot, "dist/standalone/node_modules/typescript/package.json")),
|
|
).toBe(false);
|
|
expect(
|
|
fs.existsSync(
|
|
path.join(appRoot, "dist/standalone/node_modules/vinext/dist/server/prod-server.js"),
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("copies transitive dependencies of manifest packages", () => {
|
|
const appRoot = path.join(tmpDir, "app");
|
|
fs.mkdirSync(appRoot, { recursive: true });
|
|
|
|
writeFile(appRoot, "package.json", JSON.stringify({ name: "app" }, null, 2));
|
|
writeFile(appRoot, "dist/client/_next/static/main.js", "console.log('client');\n");
|
|
writeFile(appRoot, "dist/server/entry.js", 'console.log("server");\n');
|
|
// dep-a is in the manifest; dep-b is dep-a's dependency (transitive).
|
|
writeFile(appRoot, "dist/server/vinext-externals.json", JSON.stringify(["dep-a"]));
|
|
|
|
writePackage(appRoot, "dep-a", { "dep-b": "1.0.0" });
|
|
writePackage(appRoot, "dep-b");
|
|
|
|
const fakeVinextRoot = path.join(tmpDir, "fake-vinext");
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"package.json",
|
|
JSON.stringify({ name: "vinext", type: "module" }, null, 2),
|
|
);
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"dist/server/prod-server.js",
|
|
"export async function startProdServer() {}\n",
|
|
);
|
|
|
|
const result = emitStandaloneOutput({
|
|
root: appRoot,
|
|
outDir: path.join(appRoot, "dist"),
|
|
vinextPackageRoot: fakeVinextRoot,
|
|
});
|
|
|
|
expect(result.copiedPackages).toContain("dep-a");
|
|
// Transitive dep of dep-a must also be present.
|
|
expect(result.copiedPackages).toContain("dep-b");
|
|
expect(
|
|
fs.existsSync(path.join(appRoot, "dist/standalone/node_modules/dep-a/package.json")),
|
|
).toBe(true);
|
|
expect(
|
|
fs.existsSync(path.join(appRoot, "dist/standalone/node_modules/dep-b/package.json")),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("falls back gracefully when vinext-externals.json is missing (no manifest)", () => {
|
|
const appRoot = path.join(tmpDir, "app");
|
|
fs.mkdirSync(appRoot, { recursive: true });
|
|
|
|
writeFile(appRoot, "package.json", JSON.stringify({ name: "app" }, null, 2));
|
|
writeFile(appRoot, "dist/client/_next/static/main.js", "console.log('client');\n");
|
|
writeFile(appRoot, "dist/server/entry.js", 'console.log("server");\n');
|
|
// No vinext-externals.json written.
|
|
|
|
const fakeVinextRoot = path.join(tmpDir, "fake-vinext");
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"package.json",
|
|
JSON.stringify({ name: "vinext", type: "module" }, null, 2),
|
|
);
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"dist/server/prod-server.js",
|
|
"export async function startProdServer() {}\n",
|
|
);
|
|
|
|
const result = emitStandaloneOutput({
|
|
root: appRoot,
|
|
outDir: path.join(appRoot, "dist"),
|
|
vinextPackageRoot: fakeVinextRoot,
|
|
});
|
|
|
|
// Only vinext itself should be present (always embedded).
|
|
expect(result.copiedPackages).toEqual(["vinext"]);
|
|
expect(fs.existsSync(path.join(appRoot, "dist/standalone/server.js"))).toBe(true);
|
|
});
|
|
|
|
it("throws when dist/client or dist/server are missing", () => {
|
|
const appRoot = path.join(tmpDir, "app");
|
|
fs.mkdirSync(appRoot, { recursive: true });
|
|
writeFile(appRoot, "package.json", JSON.stringify({ name: "app" }, null, 2));
|
|
|
|
expect(() =>
|
|
emitStandaloneOutput({
|
|
root: appRoot,
|
|
outDir: path.join(appRoot, "dist"),
|
|
}),
|
|
).toThrow("Run vinext build first.");
|
|
});
|
|
|
|
it("falls back when package.json is hidden by exports map", () => {
|
|
const appRoot = path.join(tmpDir, "app");
|
|
fs.mkdirSync(appRoot, { recursive: true });
|
|
|
|
writeFile(
|
|
appRoot,
|
|
"package.json",
|
|
JSON.stringify(
|
|
{
|
|
name: "app",
|
|
dependencies: {
|
|
"dep-hidden": "1.0.0",
|
|
vinext: "1.0.0",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
writeFile(appRoot, "dist/client/_next/static/main.js", "console.log('client');\n");
|
|
writeFile(appRoot, "dist/server/entry.js", "import 'dep-hidden';\n");
|
|
writeFile(appRoot, "dist/server/vinext-externals.json", JSON.stringify(["dep-hidden"]));
|
|
|
|
writePackage(appRoot, "dep-hidden", {}, { exports: { ".": "./index.js" } });
|
|
|
|
const fakeVinextRoot = path.join(tmpDir, "fake-vinext");
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"package.json",
|
|
JSON.stringify({ name: "vinext", type: "module" }, null, 2),
|
|
);
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"dist/server/prod-server.js",
|
|
"export async function startProdServer() {}\n",
|
|
);
|
|
|
|
const result = emitStandaloneOutput({
|
|
root: appRoot,
|
|
outDir: path.join(appRoot, "dist"),
|
|
vinextPackageRoot: fakeVinextRoot,
|
|
});
|
|
|
|
expect(result.copiedPackages).toContain("dep-hidden");
|
|
expect(
|
|
fs.existsSync(path.join(appRoot, "dist/standalone/node_modules/dep-hidden/package.json")),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("throws when a required runtime dependency cannot be resolved", () => {
|
|
const appRoot = path.join(tmpDir, "app");
|
|
fs.mkdirSync(appRoot, { recursive: true });
|
|
|
|
writeFile(
|
|
appRoot,
|
|
"package.json",
|
|
JSON.stringify(
|
|
{
|
|
name: "app",
|
|
dependencies: {
|
|
"missing-required": "1.0.0",
|
|
vinext: "1.0.0",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
writeFile(appRoot, "dist/client/_next/static/main.js", "console.log('client');\n");
|
|
writeFile(appRoot, "dist/server/entry.js", "console.log('server');\n");
|
|
// missing-required is in the manifest but not installed in node_modules.
|
|
writeFile(appRoot, "dist/server/vinext-externals.json", JSON.stringify(["missing-required"]));
|
|
|
|
const fakeVinextRoot = path.join(tmpDir, "fake-vinext");
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"package.json",
|
|
JSON.stringify({ name: "vinext", type: "module" }, null, 2),
|
|
);
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"dist/server/prod-server.js",
|
|
"export async function startProdServer() {}\n",
|
|
);
|
|
|
|
expect(() =>
|
|
emitStandaloneOutput({
|
|
root: appRoot,
|
|
outDir: path.join(appRoot, "dist"),
|
|
vinextPackageRoot: fakeVinextRoot,
|
|
}),
|
|
).toThrow('Failed to resolve required runtime dependency "missing-required"');
|
|
});
|
|
|
|
it("copies vinext's own runtime dependencies into standalone node_modules", () => {
|
|
const appRoot = path.join(tmpDir, "app");
|
|
fs.mkdirSync(appRoot, { recursive: true });
|
|
|
|
writeFile(appRoot, "package.json", JSON.stringify({ name: "app" }, null, 2));
|
|
writeFile(appRoot, "dist/client/_next/static/main.js", "console.log('client');\n");
|
|
writeFile(appRoot, "dist/server/entry.js", 'console.log("server");\n');
|
|
// No app-level externals; the manifest is empty.
|
|
writeFile(appRoot, "dist/server/vinext-externals.json", JSON.stringify([]));
|
|
|
|
// Set up a fake vinext package that has its own runtime dependency (rsc-html-stream),
|
|
// which the app does NOT depend on but vinext's prod-server needs at runtime.
|
|
const fakeVinextRoot = path.join(tmpDir, "fake-vinext");
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"package.json",
|
|
JSON.stringify(
|
|
{
|
|
name: "vinext",
|
|
version: "0.0.0-test",
|
|
type: "module",
|
|
dependencies: {
|
|
"rsc-html-stream": "1.0.0",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"dist/server/prod-server.js",
|
|
"export async function startProdServer() {}\n",
|
|
);
|
|
// Install rsc-html-stream in the fake vinext's node_modules so it can be resolved.
|
|
writePackage(fakeVinextRoot, "rsc-html-stream");
|
|
|
|
const result = emitStandaloneOutput({
|
|
root: appRoot,
|
|
outDir: path.join(appRoot, "dist"),
|
|
vinextPackageRoot: fakeVinextRoot,
|
|
});
|
|
|
|
// vinext's own runtime dep must be present even though the app doesn't list it.
|
|
expect(result.copiedPackages).toContain("rsc-html-stream");
|
|
expect(
|
|
fs.existsSync(
|
|
path.join(appRoot, "dist/standalone/node_modules/rsc-html-stream/package.json"),
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("copies vinext runtime dependencies that only expose subpath exports", () => {
|
|
const appRoot = path.join(tmpDir, "app");
|
|
fs.mkdirSync(appRoot, { recursive: true });
|
|
|
|
writeFile(appRoot, "package.json", JSON.stringify({ name: "app" }, null, 2));
|
|
writeFile(appRoot, "dist/client/_next/static/main.js", "console.log('client');\n");
|
|
writeFile(appRoot, "dist/server/entry.js", 'console.log("server");\n');
|
|
writeFile(appRoot, "dist/server/vinext-externals.json", JSON.stringify([]));
|
|
|
|
const fakeVinextRoot = path.join(tmpDir, "fake-vinext");
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"package.json",
|
|
JSON.stringify(
|
|
{
|
|
name: "vinext",
|
|
version: "0.0.0-test",
|
|
type: "module",
|
|
dependencies: {
|
|
"rsc-html-stream": "1.0.0",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"dist/server/prod-server.js",
|
|
"export async function startProdServer() {}\n",
|
|
);
|
|
writePackage(fakeVinextRoot, "rsc-html-stream", {}, { exports: { "./server": "./server.js" } });
|
|
writeFile(fakeVinextRoot, "node_modules/rsc-html-stream/server.js", "export {};\n");
|
|
|
|
const result = emitStandaloneOutput({
|
|
root: appRoot,
|
|
outDir: path.join(appRoot, "dist"),
|
|
vinextPackageRoot: fakeVinextRoot,
|
|
});
|
|
|
|
expect(result.copiedPackages).toContain("rsc-html-stream");
|
|
expect(
|
|
fs.existsSync(
|
|
path.join(appRoot, "dist/standalone/node_modules/rsc-html-stream/package.json"),
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("copies packages referenced through symlinked node_modules entries", () => {
|
|
const appRoot = path.join(tmpDir, "app");
|
|
fs.mkdirSync(appRoot, { recursive: true });
|
|
|
|
writeFile(
|
|
appRoot,
|
|
"package.json",
|
|
JSON.stringify(
|
|
{
|
|
name: "app",
|
|
dependencies: {
|
|
"dep-link": "1.0.0",
|
|
vinext: "1.0.0",
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
writeFile(appRoot, "dist/client/_next/static/main.js", "console.log('client');\n");
|
|
writeFile(appRoot, "dist/server/entry.js", "import 'dep-link';\n");
|
|
writeFile(appRoot, "dist/server/vinext-externals.json", JSON.stringify(["dep-link"]));
|
|
|
|
const storeDir = path.join(tmpDir, "store", "dep-link");
|
|
fs.mkdirSync(storeDir, { recursive: true });
|
|
writeFile(
|
|
storeDir,
|
|
"package.json",
|
|
JSON.stringify({ name: "dep-link", version: "1.0.0", main: "index.js" }, null, 2),
|
|
);
|
|
writeFile(storeDir, "index.js", "module.exports = {};\n");
|
|
|
|
const nodeModulesDir = path.join(appRoot, "node_modules");
|
|
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
fs.symlinkSync(storeDir, path.join(nodeModulesDir, "dep-link"), "dir");
|
|
|
|
const fakeVinextRoot = path.join(tmpDir, "fake-vinext");
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"package.json",
|
|
JSON.stringify({ name: "vinext", type: "module" }, null, 2),
|
|
);
|
|
writeFile(
|
|
fakeVinextRoot,
|
|
"dist/server/prod-server.js",
|
|
"export async function startProdServer() {}\n",
|
|
);
|
|
|
|
emitStandaloneOutput({
|
|
root: appRoot,
|
|
outDir: path.join(appRoot, "dist"),
|
|
vinextPackageRoot: fakeVinextRoot,
|
|
});
|
|
|
|
expect(
|
|
fs.existsSync(path.join(appRoot, "dist/standalone/node_modules/dep-link/package.json")),
|
|
).toBe(true);
|
|
expect(
|
|
fs.lstatSync(path.join(appRoot, "dist/standalone/node_modules/dep-link")).isSymbolicLink(),
|
|
).toBe(false);
|
|
});
|
|
});
|