Skip to content

Commit 22137f3

Browse files
authored
feat(TS): auto-generate TypeScript definitions and add typecheck script (#176)
* feat(TS): auto-generate TypeScript definitions and add typecheck script * add include * configure a few more things
1 parent a1b0371 commit 22137f3

File tree

10 files changed

+194
-79
lines changed

10 files changed

+194
-79
lines changed

README.md

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ for linting, testing, building, and more.
3535
- [Installation](#installation)
3636
- [Usage](#usage)
3737
- [Overriding Config](#overriding-config)
38-
- [Flow support](#flow-support)
3938
- [TypeScript Support](#typescript-support)
4039
- [Inspiration](#inspiration)
4140
- [Other Solutions](#other-solutions)
@@ -113,27 +112,23 @@ module.exports = Object.assign(jestConfig, {
113112
> configuring things to make it less magical and more straightforward. Extending
114113
> can take place on your terms. I think this is actually a great way to do this.
115114
116-
### Flow support
117-
118-
If the `flow-bin` is a dependency on the project the `@babel/preset-flow` will
119-
automatically get loaded when you use the default babel config that comes with
120-
`kcd-scripts`. If you customised your `.babelrc`-file you might need to manually
121-
add `@babel/preset-flow` to the `presets`-section.
122-
123115
### TypeScript Support
124116

125117
If the `tsconfig.json`-file is present in the project root directory and
126118
`typescript` is a dependency the `@babel/preset-typescript` will automatically
127119
get loaded when you use the default babel config that comes with `kcd-scripts`.
128-
If you customised your `.babelrc`-file you might need to manually add
120+
If you customized your `.babelrc`-file you might need to manually add
129121
`@babel/preset-typescript` to the `presets`-section.
130122

131123
`kcd-scripts` will automatically load any `.ts` and `.tsx` files, including the
132124
default entry point, so you don't have to worry about any rollup configuration.
133125

134-
`tsc --build tsconfig.json` will run during before committing to verify that
135-
files will compile. So make sure to add the `noEmit` flag to the
136-
`tsconfig.json`'s `compilerOptions`.
126+
If you have a `typecheck` script (normally set to `kcd-scripts typecheck`) that
127+
will be run as part of the `validate` script (which is run as part of the
128+
`pre-commit` script as well).
129+
130+
TypeScript definition files will also automatically be generated during the
131+
`build` script.
137132

138133
## Inspiration
139134

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"eslint.js",
2626
"husky.js",
2727
"jest.js",
28-
"prettier.js"
28+
"prettier.js",
29+
"shared-tsconfig.json"
2930
],
3031
"keywords": [],
3132
"author": "Kent C. Dodds <[email protected]> (https://kentcdodds.com)",

shared-tsconfig.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"exclude": ["node_modules"],
3+
"include": ["../../src/**/*"],
4+
"compilerOptions": {
5+
"isolatedModules": true,
6+
"esModuleInterop": true,
7+
"moduleResolution": "node",
8+
"noEmit": true,
9+
"strict": true,
10+
"jsx": "react",
11+
"skipLibCheck": true,
12+
"forceConsistentCasingInFileNames": true,
13+
"baseUrl": "../../src",
14+
"paths": {
15+
"*": ["*", "../tests/*"]
16+
},
17+
"preserveWatchOutput": true,
18+
"incremental": true,
19+
"tsBuildInfoFile": "../.cache/kcd-scripts/.tsbuildinfo"
20+
}
21+
}

src/__tests__/__snapshots__/index.js.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Available Scripts:
3434
lint
3535
pre-commit
3636
test
37+
typecheck
3738
validate
3839
3940
Options:

src/run-script.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,15 @@ function spawnScript() {
5656
// get all the arguments of the script and find the position of our script commands
5757
const args = process.argv.slice(2)
5858
const scriptIndex = args.findIndex(x =>
59-
['build', 'format', 'lint', 'pre-commit', 'test', 'validate'].includes(x),
59+
[
60+
'build',
61+
'format',
62+
'lint',
63+
'pre-commit',
64+
'test',
65+
'validate',
66+
'typecheck',
67+
].includes(x),
6068
)
6169

6270
// Extract the node arguments so we can pass them to node later on

src/scripts/build/babel.js

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ const spawn = require('cross-spawn')
44
const yargsParser = require('yargs-parser')
55
const rimraf = require('rimraf')
66
const glob = require('glob')
7-
const {hasPkgProp, fromRoot, resolveBin, hasFile} = require('../../utils')
7+
const {
8+
hasPkgProp,
9+
fromRoot,
10+
resolveBin,
11+
hasFile,
12+
hasTypescript,
13+
generateTypeDefs,
14+
} = require('../../utils')
815

916
let args = process.argv.slice(2)
1017
const here = p => path.join(__dirname, p)
@@ -42,25 +49,42 @@ if (!useSpecifiedOutDir && !args.includes('--no-clean')) {
4249
args = args.filter(a => a !== '--no-clean')
4350
}
4451

45-
const result = spawn.sync(
46-
resolveBin('@babel/cli', {executable: 'babel'}),
47-
[...outDir, ...copyFiles, ...ignore, ...extensions, ...config, 'src'].concat(
48-
args,
49-
),
50-
{stdio: 'inherit'},
51-
)
52+
function go() {
53+
let result = spawn.sync(
54+
resolveBin('@babel/cli', {executable: 'babel'}),
55+
[
56+
...outDir,
57+
...copyFiles,
58+
...ignore,
59+
...extensions,
60+
...config,
61+
'src',
62+
].concat(args),
63+
{stdio: 'inherit'},
64+
)
65+
if (result.status !== 0) return result.status
5266

53-
// because babel will copy even ignored files, we need to remove the ignored files
54-
const pathToOutDir = fromRoot(parsedArgs.outDir || builtInOutDir)
55-
const ignoredPatterns = (parsedArgs.ignore || builtInIgnore)
56-
.split(',')
57-
.map(pattern => path.join(pathToOutDir, pattern))
58-
const ignoredFiles = ignoredPatterns.reduce(
59-
(all, pattern) => [...all, ...glob.sync(pattern)],
60-
[],
61-
)
62-
ignoredFiles.forEach(ignoredFile => {
63-
rimraf.sync(ignoredFile)
64-
})
67+
if (hasTypescript && !args.includes('--no-ts-defs')) {
68+
console.log('Generating TypeScript definitions')
69+
result = generateTypeDefs()
70+
console.log('TypeScript definitions generated')
71+
if (result.status !== 0) return result.status
72+
}
6573

66-
process.exit(result.status)
74+
// because babel will copy even ignored files, we need to remove the ignored files
75+
const pathToOutDir = fromRoot(parsedArgs.outDir || builtInOutDir)
76+
const ignoredPatterns = (parsedArgs.ignore || builtInIgnore)
77+
.split(',')
78+
.map(pattern => path.join(pathToOutDir, pattern))
79+
const ignoredFiles = ignoredPatterns.reduce(
80+
(all, pattern) => [...all, ...glob.sync(pattern)],
81+
[],
82+
)
83+
ignoredFiles.forEach(ignoredFile => {
84+
rimraf.sync(ignoredFile)
85+
})
86+
87+
return result.status
88+
}
89+
90+
process.exit(go())

src/scripts/build/rollup.js

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const path = require('path')
2+
const fs = require('fs')
23
const spawn = require('cross-spawn')
34
const glob = require('glob')
45
const rimraf = require('rimraf')
@@ -9,6 +10,8 @@ const {
910
fromRoot,
1011
getConcurrentlyArgs,
1112
writeExtraEntry,
13+
hasTypescript,
14+
generateTypeDefs,
1215
} = require('../../utils')
1316

1417
const crossEnv = resolveBin('cross-env')
@@ -46,9 +49,9 @@ const getCommand = (env, ...flags) =>
4649
.join(' ')
4750

4851
const buildPreact = args.includes('--p-react')
49-
const scripts = buildPreact
50-
? getPReactScripts()
51-
: getConcurrentlyArgs(getCommands())
52+
const scripts = getConcurrentlyArgs(
53+
buildPreact ? getPReactCommands() : getCommands(),
54+
)
5255

5356
const cleanBuildDirs = !args.includes('--no-clean')
5457

@@ -60,25 +63,56 @@ if (cleanBuildDirs) {
6063
}
6164
}
6265

63-
const result = spawn.sync(resolveBin('concurrently'), scripts, {
64-
stdio: 'inherit',
65-
})
66-
67-
if (result.status === 0 && buildPreact && !args.includes('--no-package-json')) {
68-
writeExtraEntry(
69-
'preact',
70-
{
71-
cjs: glob.sync(fromRoot('preact/**/*.cjs.js'))[0],
72-
esm: glob.sync(fromRoot('preact/**/*.esm.js'))[0],
73-
},
74-
false,
75-
)
66+
function go() {
67+
let result = spawn.sync(resolveBin('concurrently'), scripts, {
68+
stdio: 'inherit',
69+
})
70+
71+
if (result.status !== 0) return result.status
72+
73+
if (buildPreact && !args.includes('--no-package-json')) {
74+
writeExtraEntry(
75+
'preact',
76+
{
77+
cjs: glob.sync(fromRoot('preact/**/*.cjs.js'))[0],
78+
esm: glob.sync(fromRoot('preact/**/*.esm.js'))[0],
79+
},
80+
false,
81+
)
82+
}
83+
84+
if (hasTypescript && !args.includes('--no-ts-defs')) {
85+
console.log('Generating TypeScript definitions')
86+
result = generateTypeDefs()
87+
if (result.status !== 0) return result.status
88+
89+
for (const format of formats) {
90+
const [formatFile] = glob.sync(fromRoot(`dist/*.${format}.js`))
91+
const {name} = path.parse(formatFile)
92+
// make a .d.ts file for every generated file that re-exports index.d.ts
93+
fs.writeFileSync(fromRoot('dist', `${name}.d.ts`), 'export * from ".";\n')
94+
}
95+
96+
// because typescript generates type defs for ignored files, we need to
97+
// remove the ignored files
98+
const ignoredFiles = [
99+
...glob.sync(fromRoot('dist', '**/__tests__/**')),
100+
...glob.sync(fromRoot('dist', '**/__mocks__/**')),
101+
]
102+
ignoredFiles.forEach(ignoredFile => {
103+
rimraf.sync(ignoredFile)
104+
})
105+
console.log('TypeScript definitions generated')
106+
}
107+
108+
return result.status
76109
}
77110

78-
function getPReactScripts() {
79-
const reactCommands = prefixKeys('react.', getCommands())
80-
const preactCommands = prefixKeys('preact.', getCommands({preact: true}))
81-
return getConcurrentlyArgs(Object.assign(reactCommands, preactCommands))
111+
function getPReactCommands() {
112+
return {
113+
...prefixKeys('react.', getCommands()),
114+
...prefixKeys('preact.', getCommands({preact: true})),
115+
}
82116
}
83117

84118
function prefixKeys(prefix, object) {
@@ -111,4 +145,4 @@ function getCommands({preact = false} = {}) {
111145
}, {})
112146
}
113147

114-
process.exit(result.status)
148+
process.exit(go())

src/scripts/pre-commit.js

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const path = require('path')
22
const spawn = require('cross-spawn')
3-
const {hasPkgProp, hasFile, resolveBin, hasTypescript} = require('../utils')
3+
const {hasPkgProp, hasFile, resolveBin} = require('../utils')
44

55
const here = p => path.join(__dirname, p)
66
const hereRelative = p => here(p).replace(process.cwd(), '.')
@@ -17,30 +17,20 @@ const config = useBuiltInConfig
1717
? ['--config', hereRelative('../config/lintstagedrc.js')]
1818
: []
1919

20-
const lintStagedResult = spawn.sync(
21-
resolveBin('lint-staged'),
22-
[...config, ...args],
23-
{stdio: 'inherit'},
24-
)
20+
function go() {
21+
let result
2522

26-
if (lintStagedResult.status !== 0) {
27-
process.exit(lintStagedResult.status)
28-
}
23+
result = spawn.sync(resolveBin('lint-staged'), [...config, ...args], {
24+
stdio: 'inherit',
25+
})
2926

30-
if (hasTypescript) {
31-
const tscResult = spawn.sync(
32-
resolveBin('typescript', {executable: 'tsc'}),
33-
['--build', 'tsconfig.json'],
34-
{stdio: 'inherit'},
35-
)
27+
if (result.status !== 0) return result.status
3628

37-
if (tscResult.status !== 0) {
38-
process.exit(tscResult.status)
39-
}
40-
}
29+
result = spawn.sync('npm', ['run', 'validate'], {
30+
stdio: 'inherit',
31+
})
4132

42-
const validateResult = spawn.sync('npm', ['run', 'validate'], {
43-
stdio: 'inherit',
44-
})
33+
return result.status
34+
}
4535

46-
process.exit(validateResult.status)
36+
process.exit(go())

src/scripts/typecheck.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const spawn = require('cross-spawn')
2+
const {hasAnyDep, resolveBin, hasFile} = require('../utils')
3+
4+
const args = process.argv.slice(2)
5+
6+
if (!hasAnyDep('typescript')) {
7+
throw new Error(
8+
'Cannot use the "typecheck" script in a project that does not have typescript listed as a dependency (or devDependency).',
9+
)
10+
}
11+
12+
if (!hasFile('tsconfig.json')) {
13+
throw new Error(
14+
'Cannot use the "typecheck" script in a project that does not have a tsconfig.json file.',
15+
)
16+
}
17+
18+
const result = spawn.sync(
19+
resolveBin('typescript', {executable: 'tsc'}),
20+
['--build', ...args],
21+
{stdio: 'inherit'},
22+
)
23+
24+
process.exit(result.status)

0 commit comments

Comments
 (0)