mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 16:34:36 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab3cf9d69a | ||
|
|
ae6910cadb | ||
|
|
a9a140f6ea | ||
|
|
555191f95d | ||
|
|
b86cbd38e5 |
@@ -36,6 +36,23 @@ function containsPath(directory: string, filePath: string): boolean {
|
||||
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
||||
}
|
||||
|
||||
// @types/node can declare builtins newer than the host Node (node:stream/iter on Node 24).
|
||||
function isNodeSpecifier(specifier: string): boolean {
|
||||
return isBuiltin(specifier) || specifier.startsWith("node:") || specifier === "@types/node";
|
||||
}
|
||||
|
||||
function declaredPackage(input: {
|
||||
specifier: string;
|
||||
kind: PluginImportKind;
|
||||
typeOnly: boolean;
|
||||
}): string {
|
||||
const { specifier, kind, typeOnly } = input;
|
||||
if (kind === "type-reference") return `@types/${specifier.replace(/^@/, "").replace("/", "__")}`;
|
||||
// Declarations load Node's ambient types with `import "node"`.
|
||||
if (typeOnly && specifier === "node") return "@types/node";
|
||||
return specifier;
|
||||
}
|
||||
|
||||
function dependencyName(specifier: string): string {
|
||||
const segments = specifier.split("/");
|
||||
return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
|
||||
@@ -180,10 +197,7 @@ function createRuntimeBoundaryPlugin(target: PluginBuildTarget, pluginDirectory:
|
||||
if (checked.has(key) || !/\.[cm]?[jt]sx?$/.test(file)) return null;
|
||||
checked.add(key);
|
||||
for (const { specifier, kind, typeOnly } of imports.read(file)) {
|
||||
const packageSpecifier =
|
||||
kind === "type-reference"
|
||||
? `@types/${specifier.replace(/^@/, "").replace("/", "__")}`
|
||||
: specifier;
|
||||
const packageSpecifier = declaredPackage({ specifier, kind, typeOnly });
|
||||
const error =
|
||||
runtimeSpecifierError(specifier, owner, file) ??
|
||||
runtimeSpecifierError(packageSpecifier, owner, file) ??
|
||||
@@ -197,7 +211,7 @@ function createRuntimeBoundaryPlugin(target: PluginBuildTarget, pluginDirectory:
|
||||
if (
|
||||
(PLUGIN_SDK_SPECIFIERS as readonly string[]).includes(specifier) ||
|
||||
/^(zod|react|react-native|@tanstack\/react-query)(\/|$)/.test(specifier) ||
|
||||
isBuiltin(specifier) ||
|
||||
isNodeSpecifier(specifier) ||
|
||||
packageSpecifier === "@types/node"
|
||||
)
|
||||
continue;
|
||||
@@ -288,8 +302,7 @@ function runtimeSpecifierError(
|
||||
!(PLUGIN_SDK_SPECIFIERS as readonly string[]).includes(specifier)
|
||||
)
|
||||
kind = "Unknown SDK";
|
||||
else if (target !== "server" && (isBuiltin(specifier) || specifier === "@types/node"))
|
||||
kind = "Node";
|
||||
else if (target !== "server" && isNodeSpecifier(specifier)) kind = "Node";
|
||||
else if (target !== "server" && isPluginServerOnlySdkSpecifier(specifier)) kind = "server-only";
|
||||
else if (
|
||||
target !== "client" &&
|
||||
|
||||
@@ -389,6 +389,46 @@ export type Value = string;`,
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ name: "import 'node' with Node types installed", source: 'import "node";', nodeTypes: true },
|
||||
{ name: "import 'node' without Node types", source: 'import "node";', nodeTypes: false },
|
||||
{
|
||||
name: "a node: builtin unknown to the host",
|
||||
source: 'import type { Future } from "node:paseo-future-builtin";',
|
||||
nodeTypes: false,
|
||||
},
|
||||
])("treats Node declarations as host types in server dependencies: $name", async (fixture) => {
|
||||
const entries = await createSplitPlugin();
|
||||
const dependency = path.join(entries.directory, "node_modules/image-codec");
|
||||
await mkdir(dependency, { recursive: true });
|
||||
await writeFile(
|
||||
path.join(dependency, "package.json"),
|
||||
JSON.stringify({ name: "image-codec", main: "index.js", types: "index.d.ts" }),
|
||||
);
|
||||
await writeFile(path.join(dependency, "index.js"), 'module.exports = "decoded";');
|
||||
await writeFile(
|
||||
path.join(dependency, "index.d.ts"),
|
||||
`${fixture.source}\ndeclare const value: string;\nexport = value;`,
|
||||
);
|
||||
if (fixture.nodeTypes) {
|
||||
const nodeTypes = path.join(entries.directory, "node_modules/@types/node");
|
||||
await mkdir(nodeTypes, { recursive: true });
|
||||
await writeFile(
|
||||
path.join(nodeTypes, "package.json"),
|
||||
JSON.stringify({ name: "@types/node", types: "index.d.ts" }),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(nodeTypes, "index.d.ts"),
|
||||
'declare module "node:paseo-future-builtin" { export * from "node:stream"; }\nimport type { Future } from "node:paseo-future-builtin";',
|
||||
);
|
||||
}
|
||||
await writeFile(entries.server, 'import value from "image-codec"; export default value;');
|
||||
|
||||
await expect(compilePlugin({ client: null, server: entries.server })).resolves.toMatchObject({
|
||||
serverBundle: expect.stringContaining("decoded"),
|
||||
});
|
||||
});
|
||||
|
||||
it("checks the physical owner of type-only symlinks", async () => {
|
||||
const entries = await createSplitPlugin();
|
||||
await writeFile(
|
||||
|
||||
Reference in New Issue
Block a user