diff --git a/.yarn/versions/dca3f277.yml b/.yarn/versions/dca3f277.yml new file mode 100644 index 00000000000..04499aa94f5 --- /dev/null +++ b/.yarn/versions/dca3f277.yml @@ -0,0 +1,23 @@ +releases: + "@yarnpkg/cli": minor + "@yarnpkg/plugin-typescript": minor + +declined: + - "@yarnpkg/plugin-compat" + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@yarnpkg/plugin-essentials" + - "@yarnpkg/plugin-init" + - "@yarnpkg/plugin-interactive-tools" + - "@yarnpkg/plugin-nm" + - "@yarnpkg/plugin-npm-cli" + - "@yarnpkg/plugin-pack" + - "@yarnpkg/plugin-patch" + - "@yarnpkg/plugin-pnp" + - "@yarnpkg/plugin-pnpm" + - "@yarnpkg/plugin-stage" + - "@yarnpkg/plugin-version" + - "@yarnpkg/plugin-workspace-tools" + - "@yarnpkg/builder" + - "@yarnpkg/core" + - "@yarnpkg/doctor" diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/plugins/plugin-typescript.test.ts b/packages/acceptance-tests/pkg-tests-specs/sources/plugins/plugin-typescript.test.ts index e247fdb7c54..ff5e9ffe61d 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/plugins/plugin-typescript.test.ts +++ b/packages/acceptance-tests/pkg-tests-specs/sources/plugins/plugin-typescript.test.ts @@ -27,7 +27,7 @@ describe(`Plugins`, () => { ); test( - `it should automatically enable automatic @types insertion when a tsconfig is detected`, + `it should automatically enable automatic @types insertion when a tsconfig is detected at the root of the project`, makeTemporaryEnv({}, async ({path, run, source}) => { await xfs.writeFilePromise(ppath.join(path, `tsconfig.json`), ``); @@ -44,6 +44,91 @@ describe(`Plugins`, () => { }), ); + test( + `it should automatically enable automatic @types insertion when a tsconfig is detected in the current workspace`, + makeTemporaryMonorepoEnv({ + workspaces: [`packages/*`], + }, {[`packages/foo`]: {}}, async ({path, run, source}) => { + await xfs.writeFilePromise(ppath.join(path, `packages/foo/tsconfig.json`), ``); + + await run(`add`, `is-number`, { + cwd: `${path}/packages/foo` as PortablePath, + }); + + await expect(readManifest(`${path}/packages/foo` as PortablePath)).resolves.toMatchObject({ + dependencies: { + [`is-number`]: `^2.0.0`, + }, + devDependencies: { + [`@types/is-number`]: `^2`, + }, + }); + }), + ); + + test( + `it should automatically enable automatic @types insertion in the current workspace when tsEnableAutoTypes is set to true`, + makeTemporaryMonorepoEnv({ + workspaces: [`packages/*`], + }, {[`packages/foo`]: {}}, async ({path, run, source}) => { + await run(`config`, `set`, `tsEnableAutoTypes`, `true`); + + await run(`add`, `is-number`, { + cwd: `${path}/packages/foo` as PortablePath, + }); + + await expect(readManifest(`${path}/packages/foo` as PortablePath)).resolves.toMatchObject({ + dependencies: { + [`is-number`]: `^2.0.0`, + }, + devDependencies: { + [`@types/is-number`]: `^2`, + }, + }); + }), + ); + + test( + `it should not automatically enable automatic @types insertion when a tsconfig is present in a sibling workspace`, + makeTemporaryMonorepoEnv({ + workspaces: [`packages/*`], + }, {[`packages/foo`]: {}, [`packages/bar`]: {}}, async ({path, run, source}) => { + await xfs.writeFilePromise(ppath.join(path, `packages/foo/tsconfig.json`), ``); + + await run(`add`, `is-number`, { + cwd: `${path}/packages/bar` as PortablePath, + }); + + await expect(readManifest(`${path}/packages/bar` as PortablePath)).resolves.toMatchObject({ + dependencies: { + [`is-number`]: `^2.0.0`, + }, + }); + }), + ); + + test( + `it should automatically enable automatic @types insertion when a tsconfig is detected in the root project of the current workspace`, + makeTemporaryMonorepoEnv({ + workspaces: [`packages/*`], + }, {[`packages/foo`]: {}}, async ({path, run, source}) => { + await xfs.writeFilePromise(ppath.join(path, `tsconfig.json`), ``); + + await run(`add`, `is-number`, { + cwd: `${path}/packages/foo` as PortablePath, + }); + + await expect(readManifest(`${path}/packages/foo` as PortablePath)).resolves.toMatchObject({ + dependencies: { + [`is-number`]: `^2.0.0`, + }, + devDependencies: { + [`@types/is-number`]: `^2`, + }, + }); + }), + ); + test( `it should automatically add @types to devDependencies when package doesn't provide types`, makeTemporaryEnv({}, { @@ -297,7 +382,24 @@ describe(`Plugins`, () => { describe(`Removing types`, () => { for (const type of Manifest.allDependencies) { test( - `it should automatically remove @types from ${type}`, + `it should not automatically remove @types from ${type} without tsconfig or tsEnableAutoTypes`, + makeTemporaryEnv(merge({ + dependencies: { + [`is-number`]: `^1.0.0`, + }, + }, { + [type]: { + [`@types/is-number`]: `1.0.0`, + }, + }), {}, async ({path, run, source}) => { + await run(`remove`, `is-number`); + + await expect(readManifest(path)).resolves.toHaveProperty(`${type}.@types/is-number`); + }), + ); + + test( + `it should automatically remove @types from ${type} with tsEnableAutoTypes set to true`, makeTemporaryEnv(merge({ dependencies: { [`is-number`]: `^1.0.0`, @@ -314,6 +416,102 @@ describe(`Plugins`, () => { await expect(readManifest(path)).resolves.not.toHaveProperty(`${type}.@types/is-number`); }), ); + + test( + `it should automatically remove @types from ${type} with tsconfig.json present`, + makeTemporaryEnv(merge({ + dependencies: { + [`is-number`]: `^1.0.0`, + }, + }, { + [type]: { + [`@types/is-number`]: `1.0.0`, + }, + }), {}, async ({path, run, source}) => { + await xfs.writeFilePromise(ppath.join(path, `tsconfig.json`), ``); + + await run(`remove`, `is-number`); + + await expect(readManifest(path)).resolves.not.toHaveProperty(`${type}.@types/is-number`); + }), + ); + + test( + `it should not automatically remove @types ${type} from the current workspace without tsconfig.json present or tsEnableAutoTypes`, + makeTemporaryMonorepoEnv({ + workspaces: [`packages/*`], + }, + merge( + {[`packages/foo`]: {dependencies: {[`is-number`]: `^1.0.0`}}}, + {[`packages/foo`]: {[type]: {[`@types/is-number`]: `1.0.0`}}}, + ), + async ({path, run, source}) => { + await run(`remove`, `is-number`, { + cwd: `${path}/packages/foo` as PortablePath, + }); + + await expect(readManifest(`${path}/packages/foo` as PortablePath)).resolves.toHaveProperty(`${type}.@types/is-number`); + }), + ); + + test( + `it should automatically remove @types ${type} from the current workspace when a tsconfig is detected in the root project of the current workspace`, + makeTemporaryMonorepoEnv({ + workspaces: [`packages/*`], + }, + merge( + {[`packages/foo`]: {dependencies: {[`is-number`]: `^1.0.0`}}}, + {[`packages/foo`]: {[type]: {[`@types/is-number`]: `1.0.0`}}}, + ), + async ({path, run, source}) => { + await xfs.writeFilePromise(ppath.join(path, `tsconfig.json`), ``); + + await run(`remove`, `is-number`, { + cwd: `${path}/packages/foo` as PortablePath, + }); + + await expect(readManifest(`${path}/packages/foo` as PortablePath)).resolves.not.toHaveProperty(`${type}.@types/is-number`); + }), + ); + + test( + `it should automatically remove @types ${type} from the current workspace with tsEnableAutoTypes set to true`, + makeTemporaryMonorepoEnv({ + workspaces: [`packages/*`], + }, + merge( + {[`packages/foo`]: {dependencies: {[`is-number`]: `^1.0.0`}}}, + {[`packages/foo`]: {[type]: {[`@types/is-number`]: `1.0.0`}}}, + ), + async ({path, run, source}) => { + await run(`config`, `set`, `tsEnableAutoTypes`, `true`); + await run(`remove`, `is-number`, { + cwd: `${path}/packages/foo` as PortablePath, + }); + + await expect(readManifest(`${path}/packages/foo` as PortablePath)).resolves.not.toHaveProperty(`${type}.@types/is-number`); + }), + ); + + test( + `it should automatically remove @types ${type} from the current workspace with tsconfig.json present`, + makeTemporaryMonorepoEnv({ + workspaces: [`packages/*`], + }, + merge( + {[`packages/foo`]: {dependencies: {[`is-number`]: `^1.0.0`}}}, + {[`packages/foo`]: {[type]: {[`@types/is-number`]: `1.0.0`}}}, + ), + async ({path, run, source}) => { + await xfs.writeFilePromise(ppath.join(path, `packages/foo/tsconfig.json`), ``); + + await run(`remove`, `is-number`, { + cwd: `${path}/packages/foo` as PortablePath, + }); + + await expect(readManifest(`${path}/packages/foo` as PortablePath)).resolves.not.toHaveProperty(`${type}.@types/is-number`); + }), + ); } test( diff --git a/packages/docusaurus/static/configuration/yarnrc.json b/packages/docusaurus/static/configuration/yarnrc.json index 3d9e96827df..78755d5735c 100644 --- a/packages/docusaurus/static/configuration/yarnrc.json +++ b/packages/docusaurus/static/configuration/yarnrc.json @@ -835,7 +835,7 @@ "tsEnableAutoTypes": { "_package": "@yarnpkg/plugin-typescript", "title": "Define whether to automatically install @types dependencies.", - "description": "If true, Yarn will automatically add `@types` dependencies when running `yarn add` with packages that don't provide their own typings (as reported by the Algolia npm database). This behavior is enabled by default if you have a tsconfig file at the root of your project.", + "description": "If true, Yarn will automatically add `@types` dependencies when running `yarn add` with packages that don't provide their own typings (as reported by the Algolia npm database). This behavior is enabled by default if you have a tsconfig.json file at the root of your project, or in your current workspace.", "type": "boolean", "examples": [true] }, diff --git a/packages/plugin-typescript/README.md b/packages/plugin-typescript/README.md index 2ad9172c568..b3f2ed7f774 100644 --- a/packages/plugin-typescript/README.md +++ b/packages/plugin-typescript/README.md @@ -9,6 +9,10 @@ This plugin is included by default starting from Yarn 4. +## Configuration + +This plugin is enabled by default if you have a `tsconfig.json` file at the root of your project, or in your current workspace. See [`tsEnableAutoTypes`](https://yarnpkg.com/configuration/yarnrc#tsEnableAutoTypes) for more information. + ## Example ``` diff --git a/packages/plugin-typescript/sources/index.ts b/packages/plugin-typescript/sources/index.ts index e9f21c14c40..d3b38802389 100644 --- a/packages/plugin-typescript/sources/index.ts +++ b/packages/plugin-typescript/sources/index.ts @@ -26,8 +26,10 @@ const afterWorkspaceDependencyAddition = async ( const {project} = workspace; const {configuration} = project; - const tsEnableAutoTypes = configuration.get(`tsEnableAutoTypes`) - ?? xfs.existsSync(ppath.join(project.cwd, `tsconfig.json`)); + const tsEnableAutoTypes = configuration.get(`tsEnableAutoTypes`) ?? ( + xfs.existsSync(ppath.join(workspace.cwd, `tsconfig.json`)) + || xfs.existsSync(ppath.join(project.cwd, `tsconfig.json`)) + ); if (!tsEnableAutoTypes) return; @@ -118,8 +120,10 @@ const afterWorkspaceDependencyRemoval = async ( const {project} = workspace; const {configuration} = project; - const tsEnableAutoTypes = configuration.get(`tsEnableAutoTypes`) - ?? xfs.existsSync(ppath.join(project.cwd, `tsconfig.json`)); + const tsEnableAutoTypes = configuration.get(`tsEnableAutoTypes`) ?? ( + xfs.existsSync(ppath.join(workspace.cwd, `tsconfig.json`)) + || xfs.existsSync(ppath.join(project.cwd, `tsconfig.json`)) + ); if (!tsEnableAutoTypes) return;