Skip to content

Commit 14c73d3

Browse files
committed
Implement entrypoint customization
1 parent 445b1f2 commit 14c73d3

File tree

4 files changed

+103
-21
lines changed

4 files changed

+103
-21
lines changed

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"directory": "packages/cli"
2121
},
2222
"files": [
23-
"dist"
23+
"dist/**/*.js"
2424
],
2525
"bin": {
2626
"attw": "./dist/index.js"

packages/cli/src/index.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export interface Opts {
3232
configPath?: string;
3333
ignoreRules?: string[];
3434
format: Format;
35+
36+
entrypoints?: string[];
37+
includeEntrypoints?: string[];
38+
excludeEntrypoints?: string[];
3539
}
3640

3741
program
@@ -49,17 +53,28 @@ particularly ESM-related module resolution issues.`
4953
"[file-directory-or-package-spec]",
5054
"the packed .tgz, or directory containing package.json with --pack, or package spec with --from-npm"
5155
)
52-
.option("-P, --pack", "run `npm pack` in the specified directory and delete the resulting .tgz file afterwards")
53-
.option("-p, --from-npm", "read from the npm registry instead of a local file")
54-
.addOption(new Option("-f, --format <format>", "specify the print format").choices(formats).default("table"))
55-
.option("-q, --quiet", "don't print anything to STDOUT (overrides all other options)")
56+
.option("-P, --pack", "Run `npm pack` in the specified directory and delete the resulting .tgz file afterwards")
57+
.option("-p, --from-npm", "Read from the npm registry instead of a local file")
58+
.addOption(new Option("-f, --format <format>", "Specify the print format").choices(formats).default("table"))
59+
.option("-q, --quiet", "Don't print anything to STDOUT (overrides all other options)")
60+
.option(
61+
"--entrypoints <entrypoints...>",
62+
"Specify an exhaustive list of entrypoints to check. " +
63+
'The package root is `"." Specifying this option disables automatic entrypoint discovery, ' +
64+
"and overrides the `--include-entrypoints` and `--exclude-entrypoints` options."
65+
)
66+
.option(
67+
"--include-entrypoints <entrypoints...>",
68+
"Specify entrypoints to check in addition to automatically discovered ones."
69+
)
70+
.option("--exclude-entrypoints <entrypoints...>", "Specify entrypoints to exclude from checking.")
5671
.addOption(
57-
new Option("--ignore-rules <rules...>", "specify rules to ignore").choices(Object.values(problemFlags)).default([])
72+
new Option("--ignore-rules <rules...>", "Specify rules to ignore").choices(Object.values(problemFlags)).default([])
5873
)
59-
.option("--summary, --no-summary", "whether to print summary information about the different errors")
60-
.option("--emoji, --no-emoji", "whether to use any emojis")
61-
.option("--color, --no-color", "whether to use any colors (the FORCE_COLOR env variable is also available)")
62-
.option("--config-path <path>", "path to config file (default: ./.attw.json)")
74+
.option("--summary, --no-summary", "Whether to print summary information about the different errors")
75+
.option("--emoji, --no-emoji", "Whether to use any emojis")
76+
.option("--color, --no-color", "Whether to use any colors (the FORCE_COLOR env variable is also available)")
77+
.option("--config-path <path>", "Path to config file (default: ./.attw.json)")
6378
.action(async (fileOrDirectory = ".") => {
6479
const opts = program.opts<Opts>();
6580
await readConfig(program, opts.configPath);
@@ -87,7 +102,12 @@ particularly ESM-related module resolution issues.`
87102
program.error(result.error);
88103
} else {
89104
analysis = await core.checkPackage(
90-
await core.createPackageFromNpm(`${result.data.name}@${result.data.version}`)
105+
await core.createPackageFromNpm(`${result.data.name}@${result.data.version}`),
106+
{
107+
entrypoints: opts.entrypoints,
108+
includeEntrypoints: opts.includeEntrypoints,
109+
excludeEntrypoints: opts.excludeEntrypoints,
110+
}
91111
);
92112
}
93113
} catch (error) {
@@ -136,7 +156,11 @@ particularly ESM-related module resolution issues.`
136156
}
137157
const file = await readFile(fileName);
138158
const data = new Uint8Array(file);
139-
analysis = await core.checkPackage(await core.createPackageFromTarballData(data));
159+
analysis = await core.checkPackage(await core.createPackageFromTarballData(data), {
160+
entrypoints: opts.entrypoints,
161+
includeEntrypoints: opts.includeEntrypoints,
162+
excludeEntrypoints: opts.excludeEntrypoints,
163+
});
140164
} catch (error) {
141165
handleError(error, "checking file");
142166
}

packages/cli/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
],
1010
"outDir": "./dist",
1111
"declarationDir": "./lib",
12-
"sourceMap": false,
12+
"sourceMap": true,
1313
},
1414
"include": [
1515
"src"

packages/core/src/checkPackage.ts

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,24 @@ import type { Package } from "./createPackage.js";
66
import { createCompilerHosts, type CompilerHosts, CompilerHostWrapper } from "./multiCompilerHost.js";
77
import type { CheckResult, EntrypointInfo, EntrypointResolutionAnalysis, Resolution, ResolutionKind } from "./types.js";
88

9-
export async function checkPackage(pkg: Package): Promise<CheckResult> {
9+
export interface CheckPackageOptions {
10+
/**
11+
* Exhaustive list of entrypoints to check. The package root is `"."`.
12+
* Specifying this option disables automatic entrypoint discovery,
13+
* and overrides the `includeEntrypoints` and `excludeEntrypoints` options.
14+
*/
15+
entrypoints?: string[];
16+
/**
17+
* Entrypoints to check in addition to automatically discovered ones.
18+
*/
19+
includeEntrypoints?: string[];
20+
/**
21+
* Entrypoints to exclude from checking.
22+
*/
23+
excludeEntrypoints?: (string | RegExp)[];
24+
}
25+
26+
export async function checkPackage(pkg: Package, options?: CheckPackageOptions): Promise<CheckResult> {
1027
const files = pkg.listFiles();
1128
const types = files.some(ts.hasTSFileExtension) ? "included" : false;
1229
const parts = files[0].split("/");
@@ -21,7 +38,7 @@ export async function checkPackage(pkg: Package): Promise<CheckResult> {
2138
}
2239

2340
const hosts = createCompilerHosts(pkg);
24-
const entrypointResolutions = getEntrypointInfo(packageName, pkg, hosts);
41+
const entrypointResolutions = getEntrypointInfo(packageName, pkg, hosts, options);
2542
const entrypointResolutionProblems = getEntrypointResolutionProblems(entrypointResolutions, hosts);
2643
const resolutionBasedFileProblems = getResolutionBasedFileProblems(packageName, entrypointResolutions, hosts);
2744
const fileProblems = getFileProblems(entrypointResolutions, hosts);
@@ -35,6 +52,46 @@ export async function checkPackage(pkg: Package): Promise<CheckResult> {
3552
};
3653
}
3754

55+
function getEntrypoints(fs: Package, exportsObject: any, options: CheckPackageOptions | undefined): string[] {
56+
if (options?.entrypoints) {
57+
return options.entrypoints.map((e) => formatEntrypointString(e, fs.packageName));
58+
}
59+
if (exportsObject === undefined && fs) {
60+
return getProxyDirectories(`/node_modules/${fs.packageName}`, fs);
61+
}
62+
const detectedSubpaths = getSubpaths(exportsObject);
63+
if (detectedSubpaths.length === 0) {
64+
detectedSubpaths.push(".");
65+
}
66+
const included = Array.from(
67+
new Set([
68+
...detectedSubpaths,
69+
...(options?.includeEntrypoints?.map((e) => formatEntrypointString(e, fs.packageName)) ?? []),
70+
])
71+
);
72+
if (!options?.excludeEntrypoints) {
73+
return included;
74+
}
75+
return included.filter((entrypoint) => {
76+
return !options.excludeEntrypoints!.some((exclusion) => {
77+
if (typeof exclusion === "string") {
78+
return formatEntrypointString(exclusion, fs.packageName) === entrypoint;
79+
}
80+
return exclusion.test(entrypoint);
81+
});
82+
});
83+
}
84+
85+
function formatEntrypointString(path: string, packageName: string) {
86+
return (
87+
path === "." || path.startsWith("./")
88+
? path
89+
: path.startsWith(`${packageName}/`)
90+
? `.${path.slice(packageName.length)}`
91+
: `./${path}`
92+
).trim();
93+
}
94+
3895
function getSubpaths(exportsObject: any): string[] {
3996
if (!exportsObject || typeof exportsObject !== "object" || Array.isArray(exportsObject)) {
4097
return [];
@@ -62,13 +119,14 @@ function getProxyDirectories(rootDir: string, fs: Package) {
62119
.filter((f) => f !== "./");
63120
}
64121

65-
function getEntrypointInfo(packageName: string, fs: Package, hosts: CompilerHosts): Record<string, EntrypointInfo> {
122+
function getEntrypointInfo(
123+
packageName: string,
124+
fs: Package,
125+
hosts: CompilerHosts,
126+
options: CheckPackageOptions | undefined
127+
): Record<string, EntrypointInfo> {
66128
const packageJson = JSON.parse(fs.readFile(`/node_modules/${packageName}/package.json`));
67-
const subpaths = getSubpaths(packageJson.exports);
68-
const entrypoints = subpaths.length ? subpaths : ["."];
69-
if (!packageJson.exports) {
70-
entrypoints.push(...getProxyDirectories(`/node_modules/${packageName}`, fs));
71-
}
129+
const entrypoints = getEntrypoints(fs, packageJson.exports, options);
72130
const result: Record<string, EntrypointInfo> = {};
73131
for (const entrypoint of entrypoints) {
74132
const resolutions: Record<ResolutionKind, EntrypointResolutionAnalysis> = {

0 commit comments

Comments
 (0)