version
minimal version
minimum version
typesVersions
Specifying the minimal TypeScript version supported by a project.
With typesVersions, fallbacks can be specified for earlier versions. But we cannot specify which earlier versions are not supported.
Better error reporting for users using older TypeScript versions.
execa types only support TypeScript >= 3.4 because of how the readonly keyword is used.
There should be a way for us to specify this. One way would be to extend the typesVersions field to add this feature.
Our users would then get a clear error message similar to the one produced by the engines.node field for Node.js versions. Instead at the moment, we get GitHub issues from users using older TypeScript versions.
My suggestion meets these guidelines:
I've been wanting this also. Maybe use engines for it also?
"engines": {
"tsc": "^3.4.0"
}
This already exists. If your package.json doesn't have a compatible TypeScript version, you'll see
"'package.json' does not have a 'typesVersions' entry that matches version '{0}'."
We decided not to backport the typesVersions to prior compiler versions, but the problem will eventually go away once everyone is on a version that supports typesVersions
@RyanCavanaugh this is great! However this does not seem to work for me. Steps to reproduce:
$ npm install execa
Manually edit (in node_modules/execa/package.json) the execa package.json to add support only for a future version of TypeScript:
"typesVersions": {
">=4": { "*": ["index.d.ts"] }
}
Then using index.ts:
import 'execa'
This creates no errors:
$ tsc index.ts
TypeScript 3.5.2, node 12.5.0, Ubuntu 19.04. No tsconfig.json.
Just to check while you have it set up, does this happen if index.d.ts is named something else? I think we may have some fallback logic
If I rename index.d.ts to main.d.ts, tsc still produces no output. I tried renaming index.js to main.js as well.
With the original name, the --traceResolution output is:
======== Resolving module 'execa' from '/home/ether/Desktop/index.ts'. ========
Module resolution kind is not specified, using 'NodeJs'.
Loading module 'execa' from 'node_modules' folder, target file type 'TypeScript'.
Found 'package.json' at '/home/ether/Desktop/node_modules/execa/package.json'.
'package.json' has a 'typesVersions' field with version-specific path mappings.
'package.json' does not have a 'typesVersions' entry that matches version '3.5'.
File '/home/ether/Desktop/node_modules/execa.ts' does not exist.
File '/home/ether/Desktop/node_modules/execa.tsx' does not exist.
File '/home/ether/Desktop/node_modules/execa.d.ts' does not exist.
'package.json' does not have a 'typings' field.
'package.json' does not have a 'types' field.
'package.json' does not have a 'main' field.
File '/home/ether/Desktop/node_modules/execa/index.ts' does not exist.
File '/home/ether/Desktop/node_modules/execa/index.tsx' does not exist.
File '/home/ether/Desktop/node_modules/execa/index.d.ts' exist - use it as a name resolution result.
Resolving real path for '/home/ether/Desktop/node_modules/execa/index.d.ts', result '/home/ether/Desktop/node_modules/execa/index.d.ts'.
======== Module name 'execa' was successfully resolved to '/home/ether/Desktop/node_modules/execa/index.d.ts' with Package ID 'execa/[email protected]'. ========
It does fallback to index.d.ts (which means minimal TypeScript version is not enforced). This is the output when renaming files:
======== Resolving module 'execa' from '/home/ether/Desktop/index.ts'. ========
Module resolution kind is not specified, using 'NodeJs'.
Loading module 'execa' from 'node_modules' folder, target file type 'TypeScript'.
Found 'package.json' at '/home/ether/Desktop/node_modules/execa/package.json'.
'package.json' has a 'typesVersions' field with version-specific path mappings.
'package.json' does not have a 'typesVersions' entry that matches version '3.5'.
File '/home/ether/Desktop/node_modules/execa.ts' does not exist.
File '/home/ether/Desktop/node_modules/execa.tsx' does not exist.
File '/home/ether/Desktop/node_modules/execa.d.ts' does not exist.
'package.json' does not have a 'typings' field.
'package.json' does not have a 'types' field.
'package.json' does not have a 'main' field.
File '/home/ether/Desktop/node_modules/execa/index.ts' does not exist.
File '/home/ether/Desktop/node_modules/execa/index.tsx' does not exist.
File '/home/ether/Desktop/node_modules/execa/index.d.ts' does not exist.
File '/home/ether/Desktop/node_modules/@types/execa.d.ts' does not exist.
Directory '/home/ether/node_modules' does not exist, skipping all lookups in it.
Directory '/home/node_modules' does not exist, skipping all lookups in it.
Directory '/node_modules' does not exist, skipping all lookups in it.
Loading module 'execa' from 'node_modules' folder, target file type 'JavaScript'.
Found 'package.json' at '/home/ether/Desktop/node_modules/execa/package.json'.
'package.json' has a 'typesVersions' field with version-specific path mappings.
'package.json' does not have a 'typesVersions' entry that matches version '3.5'.
File '/home/ether/Desktop/node_modules/execa.js' does not exist.
File '/home/ether/Desktop/node_modules/execa.jsx' does not exist.
'package.json' does not have a 'main' field.
File '/home/ether/Desktop/node_modules/execa/index.js' does not exist.
File '/home/ether/Desktop/node_modules/execa/index.jsx' does not exist.
Directory '/home/ether/node_modules' does not exist, skipping all lookups in it.
Directory '/home/node_modules' does not exist, skipping all lookups in it.
Directory '/node_modules' does not exist, skipping all lookups in it.
======== Module name 'execa' was not resolved. ========
It does not resolve execa but does not create any error messages.
Here's one thing you can try:
notsupported.d.ts file to execa.execa in the following way:
"types": "notsupported.d.ts",
"typesVersions": {
">=3.4.0-0": { "*": ["index.d.ts"] }
}
TypeScript versions earlier than 3.4 will load notsupported.d.ts which is empty. TypeScript versions for 3.4 onward will load index.d.ts. Attempting to import {} from 'execa' will report an error since notsupported.d.ts is not a module. However, import 'execa' will still work without error because this import form only imports the referenced file for side-effects and does not care whether the referenced file is a module.
Thanks for this tip @rbuckton! It works but some issues with this approach could be:
File '/home/ether/Desktop/node_modules/execa/notsupported.d.ts' is not a module.. Many users will probably end up still creating GitHub issues thinking there's something wrong with the types declarations.More generally speaking this would be more of a workaround. Most TypeScript projects probably have a minimal supported TypeScript version, so it might be beneficial to have a "standard" way to specify it. This could also enable tools (linters, static analysis tools) to use it, in the same way that engines.node is currently used for Node.js versions.
If we were to add a feature to disallow older versions of TypeScript, it wouldn't catch TS versions prior to the version where the feature was introduced.
It's not pretty, but another workaround would be to introduce a type error that could provide some context to the users that the TS version they are using isn't supported. Something like:
// notsupported.d.ts
type __Check<T extends ">= 3.6.1"> = T;
type __NotSupported = __Check<"This version of TypeScript">;
// error: Type '"This version of TypeScript"' does not satisfy the constraint '">= 3.6.1"'.
Thanks @rbuckton! That does provide with a better error message, so this seems like the best workaround at the moment.
Any feature related to this issue would indeed only work for users whose TS version is >= to the version that introduced that feature. I think this might still be a good idea even if it would take some time to be useful (since it requires most TypeScript users to migrate to at least that TS version first, which might take years).
As a side note, I think the following might work as well, as a slight variation of the workaround above, maybe more explicit:
"typesVersions": {
">=3.4.0-0": { "*": ["index.d.ts"] },
"*": { "*": ["notsupported.d.ts"] }
}
Keep in mind that won't catch TypeScript 3.0 and earlier
We are considering a mechanism to indicate an unsupported TypeScript version (with the caveat that earlier versions won't support this capability), but there is not enough time this iteration to implement it.
The change might be something like this:
"typesVersions": {
">=3.4.0-0": { "*": ["index.d.ts"] },
"*": "not-supported"
}
What happens if I do the opposite on my project? By default expose type declarations with a type error, and override it with the proper file for versions above 3.4.0?
Will that error for versions below 3.4.0 and behave properly for 3.4.0 and above?
Most helpful comment
I've been wanting this also. Maybe use
enginesfor it also?