mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
* feat(javascript): resolve exact package import aliases Resolve exact #alias specifiers against the importer's enclosing package.json imports map using the shared condition, array, and null target selection. Nested packages keep their own maps; missing keys, explicit nulls, and out-of-scope aliases report a precise reason. Wildcard, recursive, and external-package import targets remain unresolved. * fix(javascript): keep nested import aliases in CLI graphs Bound nested #alias requires produce application-graph require edges from the enclosing package. Node reports MODULE_NOT_FOUND when a nested package.json has no imports field, without inheriting the ancestor map. * fix(javascript): resolve literal package import keys before path admission * fix(javascript): preserve import keys and invalid-target fallback * fix(javascript): preserve unmatched package target arrays * fix(javascript): retain package target refusal evidence * test(browser): match real verifier startup budget --------- Co-authored-by: morluto <[email protected]>
655 lines
22 KiB
TypeScript
655 lines
22 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
import { createRequire } from "node:module";
|
|
import { dirname, join, relative } from "node:path";
|
|
import { promisify } from "node:util";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import {
|
|
resolveArtifactPathByContext,
|
|
type ArtifactPathResolution,
|
|
} from "../../../src/application/javascript/JavaScriptArtifactPathResolution.js";
|
|
import type { JavaScriptArtifactFile } from "../../../src/domain/javascript/javascriptArtifactFiles.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const execute = promisify(execFile);
|
|
const nodeOutcomeSchema = z.strictObject({
|
|
target: z.string().nullable(),
|
|
error_code: z.string().nullable(),
|
|
});
|
|
|
|
const posixRelative = (root: string, absolute: string): string =>
|
|
relative(root, absolute).replaceAll("\\", "/");
|
|
|
|
const inventory = (
|
|
contents: ReadonlyMap<string, string>,
|
|
): Map<string, JavaScriptArtifactFile> =>
|
|
new Map(
|
|
[...contents].map(([path, value]) => [
|
|
path,
|
|
{
|
|
path,
|
|
container_sha256: "a".repeat(64),
|
|
sha256: "b".repeat(64),
|
|
bytes: Buffer.byteLength(value),
|
|
inventory_artifact_id: path,
|
|
kind: path.endsWith("package.json") ? "package-json" : "javascript",
|
|
unpacked: false,
|
|
text: { included: true, value },
|
|
},
|
|
]),
|
|
);
|
|
|
|
const writeTree = async (
|
|
root: string,
|
|
files: Readonly<Record<string, string>>,
|
|
): Promise<Map<string, string>> => {
|
|
const contents = new Map<string, string>();
|
|
await Promise.all(
|
|
Object.entries(files).map(async ([path, value]) => {
|
|
const absolute = join(root, path);
|
|
await mkdir(dirname(absolute), { recursive: true });
|
|
await writeFile(absolute, value);
|
|
contents.set(path, value);
|
|
}),
|
|
);
|
|
return contents;
|
|
};
|
|
|
|
const nodeRequireTarget = async (
|
|
importer: string,
|
|
specifier: string,
|
|
): Promise<z.infer<typeof nodeOutcomeSchema>> => {
|
|
try {
|
|
return {
|
|
target: createRequire(importer).resolve(specifier),
|
|
error_code: null,
|
|
};
|
|
} catch (error) {
|
|
const code =
|
|
error instanceof Error && "code" in error
|
|
? z.string().parse(Reflect.get(error, "code"))
|
|
: null;
|
|
return { target: null, error_code: code };
|
|
}
|
|
};
|
|
|
|
const nodeImportTarget = async (
|
|
cwd: string,
|
|
specifier: string,
|
|
): Promise<z.infer<typeof nodeOutcomeSchema>> => {
|
|
const { stdout } = await execute(
|
|
process.execPath,
|
|
[
|
|
"--input-type=module",
|
|
"-e",
|
|
`
|
|
try {
|
|
const url = await import.meta.resolve(${JSON.stringify(specifier)});
|
|
console.log(JSON.stringify({ target: url, error_code: null }));
|
|
} catch (error) {
|
|
console.log(JSON.stringify({
|
|
target: null,
|
|
error_code: error.code ?? null,
|
|
}));
|
|
}
|
|
`,
|
|
],
|
|
{ cwd },
|
|
);
|
|
return nodeOutcomeSchema.parse(JSON.parse(stdout));
|
|
};
|
|
|
|
const resolveAlias = (
|
|
files: Map<string, JavaScriptArtifactFile>,
|
|
declaredPath: string,
|
|
sourcePath: string,
|
|
moduleKind: "import" | "require",
|
|
): ArtifactPathResolution =>
|
|
resolveArtifactPathByContext({
|
|
declaredPath,
|
|
sourcePath,
|
|
context: "module-specifier",
|
|
moduleKind,
|
|
files,
|
|
});
|
|
|
|
describe("literal package import keys compared with Node", () => {
|
|
it("matches literal imports keys containing path-like bytes in both Node loaders", async () => {
|
|
const root = await createTestTempDirectory("rea-import-alias-literal-");
|
|
const specifiers = ["#/x", "#x%2Fy", "#x%2Ey", "#x\\y", "#x\0y"];
|
|
const contents = await writeTree(root, {
|
|
"package.json": JSON.stringify({
|
|
imports: Object.fromEntries(
|
|
specifiers.map((specifier) => [specifier, "./target.cjs"]),
|
|
),
|
|
}),
|
|
"main.cjs": "module.exports = 1;",
|
|
"target.cjs": "module.exports = 1;",
|
|
});
|
|
const inventoried = inventory(contents);
|
|
for (const specifier of specifiers) {
|
|
const required = await nodeRequireTarget(
|
|
join(root, "main.cjs"),
|
|
specifier,
|
|
);
|
|
expect(required.error_code).toBeNull();
|
|
expect(posixRelative(root, required.target ?? "")).toBe("target.cjs");
|
|
const imported = await nodeImportTarget(root, specifier);
|
|
expect(imported.error_code).toBeNull();
|
|
expect(posixRelative(root, fileURLToPath(imported.target ?? ""))).toBe(
|
|
"target.cjs",
|
|
);
|
|
for (const moduleKind of ["require", "import"] as const)
|
|
expect(
|
|
resolveAlias(inventoried, specifier, "main.cjs", moduleKind),
|
|
JSON.stringify({ specifier, moduleKind }),
|
|
).toMatchObject({
|
|
declared_path: specifier,
|
|
resolution_status: "resolved",
|
|
resolved_path: "target.cjs",
|
|
});
|
|
}
|
|
});
|
|
|
|
it("still rejects unsafe selected targets for literal imports keys", async () => {
|
|
const root = await createTestTempDirectory("rea-import-alias-target-");
|
|
const contents = await writeTree(root, {
|
|
"package.json": JSON.stringify({
|
|
imports: { "#x%2Fy": "./nested%2Ftarget.cjs" },
|
|
}),
|
|
"main.cjs": "module.exports = 1;",
|
|
"nested/target.cjs": "module.exports = 1;",
|
|
});
|
|
const inventoried = inventory(contents);
|
|
const required = await nodeRequireTarget(join(root, "main.cjs"), "#x%2Fy");
|
|
expect(required.error_code).toBe("ERR_INVALID_MODULE_SPECIFIER");
|
|
const imported = await nodeImportTarget(root, "#x%2Fy");
|
|
expect(imported.error_code).toBe("ERR_INVALID_MODULE_SPECIFIER");
|
|
for (const moduleKind of ["require", "import"] as const)
|
|
expect(
|
|
resolveAlias(inventoried, "#x%2Fy", "main.cjs", moduleKind),
|
|
).toMatchObject({ resolution_status: "rejected", resolved_path: null });
|
|
});
|
|
});
|
|
|
|
describe("inactive package target arrays compared with Node", () => {
|
|
it("continues past inactive arrays in enclosing imports and exports conditions", async () => {
|
|
const root = await createTestTempDirectory("rea-inactive-package-array-");
|
|
const conditional = {
|
|
node: [{ browser: "./browser.cjs" }],
|
|
default: "./fallback.cjs",
|
|
};
|
|
const nested = {
|
|
node: [
|
|
[{ browser: "./browser.cjs" }],
|
|
{ "rea-inactive": "./inactive.cjs" },
|
|
],
|
|
default: "./fallback.cjs",
|
|
};
|
|
const contents = await writeTree(root, {
|
|
"package.json": JSON.stringify({
|
|
imports: { "#conditional": conditional, "#nested": nested },
|
|
}),
|
|
"main.cjs": "module.exports = 1;",
|
|
"fallback.cjs": "module.exports = 1;",
|
|
"node_modules/fixture/package.json": JSON.stringify({
|
|
name: "fixture",
|
|
exports: { ".": conditional, "./nested": nested },
|
|
}),
|
|
"node_modules/fixture/fallback.cjs": "module.exports = 2;",
|
|
});
|
|
const inventoried = inventory(contents);
|
|
for (const [specifier, expected] of [
|
|
["#conditional", "fallback.cjs"],
|
|
["#nested", "fallback.cjs"],
|
|
["fixture", "node_modules/fixture/fallback.cjs"],
|
|
["fixture/nested", "node_modules/fixture/fallback.cjs"],
|
|
] as const) {
|
|
const required = await nodeRequireTarget(
|
|
join(root, "main.cjs"),
|
|
specifier,
|
|
);
|
|
expect(required.error_code, specifier).toBeNull();
|
|
expect(posixRelative(root, required.target ?? "")).toBe(expected);
|
|
const imported = await nodeImportTarget(root, specifier);
|
|
expect(imported.error_code).toBeNull();
|
|
expect(posixRelative(root, fileURLToPath(imported.target ?? ""))).toBe(
|
|
expected,
|
|
);
|
|
for (const moduleKind of ["require", "import"] as const)
|
|
expect(
|
|
resolveAlias(inventoried, specifier, "main.cjs", moduleKind),
|
|
).toMatchObject({
|
|
declared_path: specifier,
|
|
resolution_status: "resolved",
|
|
resolved_path: expected,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("package target refusal evidence compared with Node", () => {
|
|
it("keeps empty, null, invalid, and unmatched array outcomes distinct", async () => {
|
|
const root = await createTestTempDirectory("rea-inactive-package-refusal-");
|
|
const inactive = { browser: "./browser.cjs" };
|
|
const contents = await writeTree(root, {
|
|
"package.json": JSON.stringify({
|
|
imports: {
|
|
"#empty-direct": [],
|
|
"#empty": { node: [], default: "./fallback.cjs" },
|
|
"#null": { node: [null, inactive], default: "./fallback.cjs" },
|
|
"#null-last": [[], null],
|
|
"#empty-last": [null, []],
|
|
"#invalid": {
|
|
node: ["../outside.cjs", inactive],
|
|
default: "./fallback.cjs",
|
|
},
|
|
"#unmatched": [inactive],
|
|
"#unmatched-nested": [
|
|
[inactive, { "rea-inactive": "./inactive.cjs" }],
|
|
],
|
|
"#unmatched-active": { node: [inactive] },
|
|
},
|
|
}),
|
|
"main.cjs": "module.exports = 1;",
|
|
"fallback.cjs": "module.exports = 1;",
|
|
});
|
|
const inventoried = inventory(contents);
|
|
for (const [specifier, errorCode, status, limitation] of [
|
|
[
|
|
"#empty-direct",
|
|
"ERR_PACKAGE_IMPORT_NOT_DEFINED",
|
|
"external",
|
|
"an empty imports target array",
|
|
],
|
|
[
|
|
"#empty",
|
|
"ERR_PACKAGE_IMPORT_NOT_DEFINED",
|
|
"external",
|
|
"an empty imports target array",
|
|
],
|
|
[
|
|
"#null",
|
|
"ERR_PACKAGE_IMPORT_NOT_DEFINED",
|
|
"external",
|
|
"an explicit null imports target",
|
|
],
|
|
[
|
|
"#null-last",
|
|
"ERR_PACKAGE_IMPORT_NOT_DEFINED",
|
|
"external",
|
|
"an explicit null imports target",
|
|
],
|
|
[
|
|
"#empty-last",
|
|
"ERR_PACKAGE_IMPORT_NOT_DEFINED",
|
|
"external",
|
|
"an empty imports target array",
|
|
],
|
|
["#invalid", "ERR_INVALID_PACKAGE_TARGET", "rejected", "../outside.cjs"],
|
|
[
|
|
"#unmatched",
|
|
"ERR_PACKAGE_IMPORT_NOT_DEFINED",
|
|
"external",
|
|
"it declares browser.",
|
|
],
|
|
[
|
|
"#unmatched-nested",
|
|
"ERR_PACKAGE_IMPORT_NOT_DEFINED",
|
|
"external",
|
|
"it declares browser, rea-inactive.",
|
|
],
|
|
[
|
|
"#unmatched-active",
|
|
"ERR_PACKAGE_IMPORT_NOT_DEFINED",
|
|
"external",
|
|
"it declares node, browser.",
|
|
],
|
|
] as const) {
|
|
expect(
|
|
(await nodeRequireTarget(join(root, "main.cjs"), specifier)).error_code,
|
|
).toBe(errorCode);
|
|
expect((await nodeImportTarget(root, specifier)).error_code).toBe(
|
|
errorCode,
|
|
);
|
|
for (const moduleKind of ["require", "import"] as const) {
|
|
const result = resolveAlias(
|
|
inventoried,
|
|
specifier,
|
|
"main.cjs",
|
|
moduleKind,
|
|
);
|
|
expect(result).toMatchObject({
|
|
resolution_status: status,
|
|
resolved_path: null,
|
|
});
|
|
expect(result.limitations.join(" "), specifier).toContain(limitation);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("package import target arrays compared with Node", () => {
|
|
it("skips invalid parent, absolute, and URL targets before an exact fallback", async () => {
|
|
const root = await createTestTempDirectory("rea-import-alias-fallback-");
|
|
const imports = {
|
|
"#parent": ["../outside.cjs", "./fallback.cjs"],
|
|
"#absolute": { node: [["/outside.cjs"], "./fallback.cjs"] },
|
|
"#url": {
|
|
import: ["https://example.invalid/outside.cjs", "./fallback.cjs"],
|
|
require: ["file:///outside.cjs", "./fallback.cjs"],
|
|
},
|
|
};
|
|
const contents = await writeTree(root, {
|
|
"package.json": JSON.stringify({ imports }),
|
|
"main.cjs": "module.exports = 1;",
|
|
"fallback.cjs": "module.exports = 1;",
|
|
});
|
|
const inventoried = inventory(contents);
|
|
for (const specifier of Object.keys(imports)) {
|
|
const required = await nodeRequireTarget(
|
|
join(root, "main.cjs"),
|
|
specifier,
|
|
);
|
|
expect(required.error_code).toBeNull();
|
|
expect(posixRelative(root, required.target ?? "")).toBe("fallback.cjs");
|
|
const imported = await nodeImportTarget(root, specifier);
|
|
expect(imported.error_code).toBeNull();
|
|
expect(posixRelative(root, fileURLToPath(imported.target ?? ""))).toBe(
|
|
"fallback.cjs",
|
|
);
|
|
for (const moduleKind of ["require", "import"] as const)
|
|
expect(
|
|
resolveAlias(inventoried, specifier, "main.cjs", moduleKind),
|
|
).toMatchObject({
|
|
declared_path: specifier,
|
|
resolution_status: "resolved",
|
|
resolved_path: "fallback.cjs",
|
|
});
|
|
}
|
|
});
|
|
|
|
it("preserves invalid, null, and unsupported-package outcomes in arrays", async () => {
|
|
const root = await createTestTempDirectory(
|
|
"rea-import-alias-fallback-refuse-",
|
|
);
|
|
const contents = await writeTree(root, {
|
|
"package.json": JSON.stringify({
|
|
imports: {
|
|
"#invalid": [null, "../outside.cjs"],
|
|
"#blocked": ["../outside.cjs", null],
|
|
"#package": ["fixture", "./fallback.cjs"],
|
|
},
|
|
}),
|
|
"main.cjs": "module.exports = 1;",
|
|
"fallback.cjs": "module.exports = 1;",
|
|
"node_modules/fixture/package.json": JSON.stringify({
|
|
name: "fixture",
|
|
main: "index.cjs",
|
|
}),
|
|
"node_modules/fixture/index.cjs": "module.exports = 2;",
|
|
});
|
|
const inventoried = inventory(contents);
|
|
for (const [specifier, errorCode, resolutionStatus, reason] of [
|
|
["#invalid", "ERR_INVALID_PACKAGE_TARGET", "rejected", "../outside.cjs"],
|
|
[
|
|
"#blocked",
|
|
"ERR_PACKAGE_IMPORT_NOT_DEFINED",
|
|
"external",
|
|
"explicit null",
|
|
],
|
|
] as const) {
|
|
const required = await nodeRequireTarget(
|
|
join(root, "main.cjs"),
|
|
specifier,
|
|
);
|
|
expect(required.error_code).toBe(errorCode);
|
|
expect((await nodeImportTarget(root, specifier)).error_code).toBe(
|
|
errorCode,
|
|
);
|
|
for (const moduleKind of ["require", "import"] as const) {
|
|
const result = resolveAlias(
|
|
inventoried,
|
|
specifier,
|
|
"main.cjs",
|
|
moduleKind,
|
|
);
|
|
expect(result).toMatchObject({
|
|
resolution_status: resolutionStatus,
|
|
resolved_path: null,
|
|
});
|
|
expect(result.limitations.join(" ")).toContain(reason);
|
|
}
|
|
}
|
|
const required = await nodeRequireTarget(
|
|
join(root, "main.cjs"),
|
|
"#package",
|
|
);
|
|
expect(posixRelative(root, required.target ?? "")).toBe(
|
|
"node_modules/fixture/index.cjs",
|
|
);
|
|
const imported = await nodeImportTarget(root, "#package");
|
|
expect(imported.error_code).toBeNull();
|
|
expect(posixRelative(root, fileURLToPath(imported.target ?? ""))).toBe(
|
|
"node_modules/fixture/index.cjs",
|
|
);
|
|
for (const moduleKind of ["require", "import"] as const) {
|
|
const result = resolveAlias(
|
|
inventoried,
|
|
"#package",
|
|
"main.cjs",
|
|
moduleKind,
|
|
);
|
|
expect(result).toMatchObject({
|
|
resolution_status: "external",
|
|
resolved_path: null,
|
|
});
|
|
expect(result.limitations.join(" ")).toContain(
|
|
"External-package import targets remain unresolved",
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("package import aliases compared with Node", () => {
|
|
it("matches Node require and import for an exact root alias", async () => {
|
|
const root = await createTestTempDirectory("rea-import-alias-root-");
|
|
const files = {
|
|
"package.json": JSON.stringify({
|
|
name: "app",
|
|
imports: { "#config": "./config.cjs" },
|
|
}),
|
|
"main.cjs": 'module.exports = require("#config");',
|
|
"config.cjs": "module.exports = 'config.cjs';",
|
|
};
|
|
const contents = await writeTree(root, files);
|
|
const inventoried = inventory(contents);
|
|
const required = await nodeRequireTarget(join(root, "main.cjs"), "#config");
|
|
expect(posixRelative(root, required.target ?? "")).toBe("config.cjs");
|
|
expect(
|
|
resolveAlias(inventoried, "#config", "main.cjs", "require"),
|
|
).toMatchObject({
|
|
resolution_status: "resolved",
|
|
resolved_path: "config.cjs",
|
|
});
|
|
const imported = await nodeImportTarget(root, "#config");
|
|
expect(imported.error_code).toBeNull();
|
|
expect(
|
|
resolveAlias(inventoried, "#config", "main.cjs", "import"),
|
|
).toMatchObject({
|
|
resolution_status: "resolved",
|
|
resolved_path: "config.cjs",
|
|
});
|
|
});
|
|
|
|
it("matches Node when a nested package has its own imports map", async () => {
|
|
const root = await createTestTempDirectory("rea-import-alias-nested-");
|
|
const files = {
|
|
"package.json": JSON.stringify({
|
|
name: "app",
|
|
imports: { "#config": "./root.cjs" },
|
|
}),
|
|
"root.cjs": "module.exports = 'root';",
|
|
"node_modules/fixture/package.json": JSON.stringify({
|
|
name: "fixture",
|
|
imports: { "#config": "./local.cjs" },
|
|
}),
|
|
"node_modules/fixture/index.cjs": 'module.exports = require("#config");',
|
|
"node_modules/fixture/local.cjs": "module.exports = 'local';",
|
|
};
|
|
const contents = await writeTree(root, files);
|
|
const inventoried = inventory(contents);
|
|
const nested = await nodeRequireTarget(
|
|
join(root, "node_modules/fixture/index.cjs"),
|
|
"#config",
|
|
);
|
|
expect(posixRelative(root, nested.target ?? "")).toBe(
|
|
"node_modules/fixture/local.cjs",
|
|
);
|
|
expect(
|
|
resolveAlias(
|
|
inventoried,
|
|
"#config",
|
|
"node_modules/fixture/index.cjs",
|
|
"require",
|
|
),
|
|
).toMatchObject({
|
|
resolution_status: "resolved",
|
|
resolved_path: "node_modules/fixture/local.cjs",
|
|
});
|
|
const fromRoot = await nodeRequireTarget(join(root, "root.cjs"), "#config");
|
|
expect(posixRelative(root, fromRoot.target ?? "")).toBe("root.cjs");
|
|
expect(
|
|
resolveAlias(inventoried, "#config", "root.cjs", "require"),
|
|
).toMatchObject({
|
|
resolution_status: "resolved",
|
|
resolved_path: "root.cjs",
|
|
});
|
|
});
|
|
|
|
it("matches Node when a nested package does not define the alias", async () => {
|
|
const root = await createTestTempDirectory("rea-import-alias-scope-");
|
|
const files = {
|
|
"package.json": JSON.stringify({
|
|
name: "app",
|
|
imports: { "#config": "./root.cjs" },
|
|
}),
|
|
"root.cjs": "module.exports = 'root';",
|
|
"node_modules/fixture/package.json": JSON.stringify({
|
|
name: "fixture",
|
|
}),
|
|
"node_modules/fixture/index.cjs": 'module.exports = require("#config");',
|
|
};
|
|
await writeTree(root, files);
|
|
const nested = await nodeRequireTarget(
|
|
join(root, "node_modules/fixture/index.cjs"),
|
|
"#config",
|
|
);
|
|
expect(nested.target).toBeNull();
|
|
// CJS reports MODULE_NOT_FOUND when the nested package.json has no
|
|
// `imports` field at all; it still does not inherit the ancestor map.
|
|
expect(nested.error_code).toBe("MODULE_NOT_FOUND");
|
|
const outcome = resolveAlias(
|
|
inventory(new Map(Object.entries(files))),
|
|
"#config",
|
|
"node_modules/fixture/index.cjs",
|
|
"require",
|
|
);
|
|
expect(outcome).toMatchObject({
|
|
resolution_status: "external",
|
|
resolved_path: null,
|
|
});
|
|
expect(outcome.limitations.join(" ")).toContain(
|
|
"node_modules/fixture/package.json",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("package import alias refusals compared with Node", () => {
|
|
it("matches Node refusal for a missing key, explicit null, and reserved specifier", async () => {
|
|
const root = await createTestTempDirectory("rea-import-alias-refuse-");
|
|
const files = {
|
|
"package.json": JSON.stringify({
|
|
name: "app",
|
|
imports: {
|
|
"#config": "./config.cjs",
|
|
"#blocked": null,
|
|
"#": "./config.cjs",
|
|
"#/": "./config.cjs",
|
|
"#config/": "./config.cjs",
|
|
},
|
|
}),
|
|
"main.cjs": "module.exports = 1;",
|
|
"config.cjs": "module.exports = 1;",
|
|
};
|
|
await writeTree(root, files);
|
|
const inventoried = inventory(new Map(Object.entries(files)));
|
|
const missing = await nodeRequireTarget(join(root, "main.cjs"), "#missing");
|
|
expect(missing.error_code).toBe("ERR_PACKAGE_IMPORT_NOT_DEFINED");
|
|
expect(
|
|
resolveAlias(inventoried, "#missing", "main.cjs", "require"),
|
|
).toMatchObject({ resolution_status: "external", resolved_path: null });
|
|
const blocked = await nodeRequireTarget(join(root, "main.cjs"), "#blocked");
|
|
expect(blocked.error_code).toBe("ERR_PACKAGE_IMPORT_NOT_DEFINED");
|
|
expect(
|
|
resolveAlias(inventoried, "#blocked", "main.cjs", "require"),
|
|
).toMatchObject({ resolution_status: "external", resolved_path: null });
|
|
for (const specifier of ["#", "#/", "#config/"]) {
|
|
const reserved = await nodeRequireTarget(
|
|
join(root, "main.cjs"),
|
|
specifier,
|
|
);
|
|
expect(reserved.error_code).toBe("ERR_INVALID_MODULE_SPECIFIER");
|
|
expect((await nodeImportTarget(root, specifier)).error_code).toBe(
|
|
"ERR_INVALID_MODULE_SPECIFIER",
|
|
);
|
|
for (const moduleKind of ["require", "import"] as const)
|
|
expect(
|
|
resolveAlias(inventoried, specifier, "main.cjs", moduleKind),
|
|
).toMatchObject({ resolution_status: "rejected", resolved_path: null });
|
|
}
|
|
});
|
|
|
|
it("matches Node when an import condition selects a different exact file", async () => {
|
|
const root = await createTestTempDirectory("rea-import-alias-cond-");
|
|
const files = {
|
|
"package.json": JSON.stringify({
|
|
name: "app",
|
|
type: "module",
|
|
imports: {
|
|
"#config": {
|
|
import: "./config.mjs",
|
|
require: "./config.cjs",
|
|
},
|
|
},
|
|
}),
|
|
"main.cjs": 'module.exports = require("#config");',
|
|
"config.cjs": "export default 'cjs'; module.exports = 'cjs';",
|
|
"config.mjs": "export default 'mjs';",
|
|
};
|
|
await writeTree(root, files);
|
|
const inventoried = inventory(new Map(Object.entries(files)));
|
|
const required = await nodeRequireTarget(join(root, "main.cjs"), "#config");
|
|
expect(posixRelative(root, required.target ?? "")).toBe("config.cjs");
|
|
expect(
|
|
resolveAlias(inventoried, "#config", "main.cjs", "require"),
|
|
).toMatchObject({
|
|
resolution_status: "resolved",
|
|
resolved_path: "config.cjs",
|
|
});
|
|
const imported = await nodeImportTarget(root, "#config");
|
|
expect(imported.error_code).toBeNull();
|
|
expect(imported.target ?? "").toContain("config.mjs");
|
|
expect(
|
|
resolveAlias(inventoried, "#config", "main.cjs", "import"),
|
|
).toMatchObject({
|
|
resolution_status: "resolved",
|
|
resolved_path: "config.mjs",
|
|
});
|
|
});
|
|
});
|