Skip to content

Commit

Permalink
Fix types & typings fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed May 29, 2023
1 parent a58f021 commit 27ce8de
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/proud-dodos-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@preconstruct/cli": minor
---

`package.json#types`/`package.json#typings` fields will now be validated and fixed. They are not needed at all for TypeScript to consume packages built using Preconstruct but when they are present it's best to fix them to avoid confusion.
95 changes: 95 additions & 0 deletions packages/cli/src/__tests__/fix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,101 @@ test("set main and module field", async () => {
`);
});

test("set types with no extra entrypoints", async () => {
let tmpPath = await testdir({
"package.json": JSON.stringify({
name: "@blah/something",
main: "index.js",
types: "invalid.d.ts",
}),
"src/index.js": "",
});

await fix(tmpPath);

expect(await getFiles(tmpPath, ["**/package.json"])).toMatchInlineSnapshot(`
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
{
"name": "@blah/something",
"main": "dist/blah-something.cjs.js",
"types": "dist/blah-something.cjs.d.ts"
}
`);
});

test("set typings with no extra entrypoints", async () => {
let tmpPath = await testdir({
"package.json": JSON.stringify({
name: "@blah/something",
main: "index.js",
typings: "invalid.d.ts",
}),
"src/index.js": "",
});

await fix(tmpPath);

expect(await getFiles(tmpPath, ["**/package.json"])).toMatchInlineSnapshot(`
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
{
"name": "@blah/something",
"main": "dist/blah-something.cjs.js",
"typings": "dist/blah-something.cjs.d.ts"
}
`);
});

test("set types field with multiple entrypoints", async () => {
let tmpPath = await testdir({
"package.json": JSON.stringify({
name: "@blah/something",
main: "index.js",
types: "invalid.d.ts",
preconstruct: {
entrypoints: ["index.js", "other.js", "deep/something.js"],
},
}),
"other/package.json": JSON.stringify({}),
"deep/something/package.json": JSON.stringify({}),
"src/index.js": "",
"src/other.js": "",
"src/deep/something.js": "",
});

await fix(tmpPath);

expect(await getFiles(tmpPath, ["**/package.json"])).toMatchInlineSnapshot(`
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ deep/something/package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
{
"main": "dist/blah-something-deep-something.cjs.js",
"types": "dist/blah-something-deep-something.cjs.d.ts"
}
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ other/package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
{
"main": "dist/blah-something-other.cjs.js",
"types": "dist/blah-something-other.cjs.d.ts"
}
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ package.json ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
{
"name": "@blah/something",
"main": "dist/blah-something.cjs.js",
"types": "dist/blah-something.cjs.d.ts",
"preconstruct": {
"entrypoints": [
"index.js",
"other.js",
"deep/something.js"
]
}
}
`);
});

test("set exports field when opt-in", async () => {
let tmpPath = await testdir({
"package.json": JSON.stringify({
Expand Down
30 changes: 30 additions & 0 deletions packages/cli/src/__tests__/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,36 @@ test("valid browser", async () => {
`);
});

test("invalid types field", async () => {
let tmpPath = await testdir({
"package.json": JSON.stringify({
name: "@blah/something",
main: "dist/blah-something.cjs.js",
types: "invalid.d.ts",
}),
"src/index.js": "",
});

await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot(
`[Error: types field is invalid, found \`"invalid.d.ts"\`, expected \`"dist/blah-something.cjs.d.ts"\`]`
);
});

test("invalid typings field", async () => {
let tmpPath = await testdir({
"package.json": JSON.stringify({
name: "@blah/something",
main: "dist/blah-something.cjs.js",
typings: "invalid.d.ts",
}),
"src/index.js": "",
});

await expect(validate(tmpPath)).rejects.toMatchInlineSnapshot(
`[Error: typings field is invalid, found \`"invalid.d.ts"\`, expected \`"dist/blah-something.cjs.d.ts"\`]`
);
});

test("monorepo single package", async () => {
let tmpPath = f.copy("monorepo-single-package");

Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export class Entrypoint extends Item<{
module?: JSONValue;
"umd:main"?: JSONValue;
browser?: JSONValue;
types?: JSONValue;
typings?: JSONValue;
exports?: Record<string, ExportsConditions | string>;
preconstruct: {
source?: JSONValue;
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/fix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ async function fixPackage(pkg: Package): Promise<() => Promise<boolean>> {
!!exportsFieldConfig,
"umd:main": pkg.entrypoints.some((x) => x.json["umd:main"] !== undefined),
browser: pkg.entrypoints.some((x) => x.json.browser !== undefined),
types: pkg.entrypoints.some((x) => x.json.types !== undefined),
typings: pkg.entrypoints.some((x) => x.json.typings !== undefined),
};

if (exportsFieldConfig?.conditions.kind === "legacy") {
Expand Down
9 changes: 8 additions & 1 deletion packages/cli/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { PKG_JSON_CONFIG_FIELD } from "./constants";
import { createPromptConfirmLoader } from "./prompt";
import chalk from "chalk";

type Field = "main" | "module" | "browser" | "umd:main" | "exports";
type Field =
| "main"
| "module"
| "browser"
| "umd:main"
| "exports"
| "types"
| "typings";

export let errors = {
noSource: (source: string) =>
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export class Package extends Item<{
return pkg;
}

setFieldOnEntrypoints(field: "main" | "browser" | "module" | "umd:main") {
setFieldOnEntrypoints(field: keyof typeof validFieldsForEntrypoint) {
this.entrypoints.forEach((entrypoint) => {
entrypoint.json = setFieldInOrder(
entrypoint.json,
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let fields = [

export function setFieldInOrder<
Obj extends { [key: string]: any },
Key extends "main" | "module" | "umd:main" | "browser" | "exports",
Key extends keyof typeof validFieldsForEntrypoint | "exports",
Val extends any
>(obj: Obj, field: Key, value: Val): Obj & { [k in Key]: Val } {
if (field in obj) {
Expand Down Expand Up @@ -277,6 +277,9 @@ export function getExportsImportUnwrappingDefaultOutputPath(
return getExportsFieldOutputPath(entrypoint, "cjs").replace(/\.js$/, ".mjs");
}

const validTypesFieldForEntrypoint = (entrypoint: MinimalEntrypoint) =>
validFieldsForEntrypoint.main(entrypoint).replace(/\.js$/, ".d.ts");

export const validFieldsForEntrypoint = {
main(entrypoint: MinimalEntrypoint) {
return getDistFilename(entrypoint, "cjs");
Expand Down Expand Up @@ -307,6 +310,8 @@ export const validFieldsForEntrypoint = {
...(entrypoint.hasModuleField && moduleBuild),
};
},
types: validTypesFieldForEntrypoint,
typings: validTypesFieldForEntrypoint,
};

export function flowTemplate(hasDefaultExport: boolean, relativePath: string) {
Expand Down
17 changes: 16 additions & 1 deletion packages/cli/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ export const isFieldValid = {
// JSON.stringify to make sure conditions are in proper order
return JSON.stringify(pkg.json.exports) === JSON.stringify(generated);
},
types(entrypoint: Entrypoint) {
return entrypoint.json.types === validFieldsForEntrypoint.types(entrypoint);
},
typings(entrypoint: Entrypoint) {
return (
entrypoint.json.typings === validFieldsForEntrypoint.typings(entrypoint)
);
},
};

export function isUmdNameSpecified(entrypoint: Entrypoint) {
Expand All @@ -55,7 +63,14 @@ function validateEntrypoint(entrypoint: Entrypoint, log: boolean) {
logger.info(infos.validEntrypoint, entrypoint.name);
}
const fatalErrors: FatalError[] = [];
for (const field of ["main", "module", "umd:main", "browser"] as const) {
for (const field of [
"main",
"module",
"umd:main",
"browser",
"types",
"typings",
] as const) {
if (field !== "main" && entrypoint.json[field] === undefined) {
continue;
}
Expand Down

0 comments on commit 27ce8de

Please sign in to comment.