kea-typegen 3.6.6 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/package.json +2 -2
- package/dist/src/__tests__/typegen.d.ts +1 -0
- package/dist/src/__tests__/typegen.js +18 -0
- package/dist/src/__tests__/typegen.js.map +1 -0
- package/dist/src/__tests__/watch.d.ts +1 -0
- package/dist/src/__tests__/watch.js +32 -0
- package/dist/src/__tests__/watch.js.map +1 -0
- package/dist/src/__tests__/write.d.ts +1 -0
- package/dist/src/__tests__/write.js +164 -0
- package/dist/src/__tests__/write.js.map +1 -0
- package/dist/src/cli/typegen.js +10 -1
- package/dist/src/cli/typegen.js.map +1 -1
- package/dist/src/print/print.js +11 -3
- package/dist/src/print/print.js.map +1 -1
- package/dist/src/typegen.d.ts +2 -0
- package/dist/src/typegen.js +65 -16
- package/dist/src/typegen.js.map +1 -1
- package/dist/src/types.d.ts +1 -0
- package/dist/src/visit/visit.js +33 -11
- package/dist/src/visit/visit.js.map +1 -1
- package/dist/src/write/writeTypeImports.js +68 -34
- package/dist/src/write/writeTypeImports.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/__tests__/typegen.ts +22 -0
- package/src/__tests__/watch.ts +44 -0
- package/src/__tests__/write.ts +211 -0
- package/src/cli/typegen.ts +10 -1
- package/src/print/print.ts +14 -5
- package/src/test-support/watch-mode-smoke.js +140 -0
- package/src/test-support/write-mode-smoke.js +147 -0
- package/src/typegen.ts +84 -22
- package/src/types.ts +2 -0
- package/src/visit/visit.ts +54 -13
- package/src/write/writeTypeImports.ts +82 -49
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { spawn } from 'child_process'
|
|
2
|
+
import * as fs from 'fs'
|
|
3
|
+
import * as os from 'os'
|
|
4
|
+
import * as path from 'path'
|
|
5
|
+
import * as ts from 'typescript'
|
|
6
|
+
import { AppOptions } from '../types'
|
|
7
|
+
import { visitProgram } from '../visit/visit'
|
|
8
|
+
import { writeTypeImports } from '../write/writeTypeImports'
|
|
9
|
+
|
|
10
|
+
test(
|
|
11
|
+
'write mode recreates the TypeScript program from scratch between passes',
|
|
12
|
+
async () => {
|
|
13
|
+
const repoRoot = path.resolve(__dirname, '..', '..')
|
|
14
|
+
const scriptPath = path.join(repoRoot, 'src/test-support/write-mode-smoke.js')
|
|
15
|
+
|
|
16
|
+
const result = await new Promise<{ code: number | null; signal: NodeJS.Signals | null; stdout: string; stderr: string }>(
|
|
17
|
+
(resolve, reject) => {
|
|
18
|
+
const child = spawn(process.execPath, [scriptPath], {
|
|
19
|
+
cwd: repoRoot,
|
|
20
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
let stdout = ''
|
|
24
|
+
let stderr = ''
|
|
25
|
+
|
|
26
|
+
child.stdout.on('data', (chunk) => {
|
|
27
|
+
stdout += chunk.toString()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
child.stderr.on('data', (chunk) => {
|
|
31
|
+
stderr += chunk.toString()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
child.on('error', reject)
|
|
35
|
+
child.on('close', (code, signal) => resolve({ code, signal, stdout, stderr }))
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
expect(result.signal).toBeNull()
|
|
40
|
+
expect(result.code).toBe(0)
|
|
41
|
+
|
|
42
|
+
const payload = JSON.parse(result.stdout.trim())
|
|
43
|
+
|
|
44
|
+
expect(payload.createProgramCalls).toBeGreaterThan(1)
|
|
45
|
+
expect(payload.reusedOldProgram).toBe(false)
|
|
46
|
+
expect(result.stderr).toBe('')
|
|
47
|
+
},
|
|
48
|
+
30000,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
test('writeTypeImports adds missing type import and kea generic', async () => {
|
|
52
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-write-imports-'))
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const logicDir = path.join(tempDir, 'src')
|
|
56
|
+
const logicPath = path.join(logicDir, 'logic.ts')
|
|
57
|
+
const keaDtsPath = path.join(tempDir, 'node_modules', 'kea', 'index.d.ts')
|
|
58
|
+
|
|
59
|
+
fs.mkdirSync(path.dirname(keaDtsPath), { recursive: true })
|
|
60
|
+
fs.mkdirSync(logicDir, { recursive: true })
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(keaDtsPath, 'export function kea<T = any>(input: any): T\n')
|
|
63
|
+
fs.writeFileSync(
|
|
64
|
+
logicPath,
|
|
65
|
+
[
|
|
66
|
+
"import { kea } from 'kea'",
|
|
67
|
+
'',
|
|
68
|
+
'export const logic = kea({',
|
|
69
|
+
' actions: () => ({',
|
|
70
|
+
' setValue: (value: string) => ({ value }),',
|
|
71
|
+
' }),',
|
|
72
|
+
'})',
|
|
73
|
+
'',
|
|
74
|
+
].join('\n'),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const program = ts.createProgram([logicPath], {
|
|
78
|
+
module: ts.ModuleKind.CommonJS,
|
|
79
|
+
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
80
|
+
target: ts.ScriptTarget.ES2020,
|
|
81
|
+
skipLibCheck: true,
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const appOptions: AppOptions = {
|
|
85
|
+
rootPath: logicDir,
|
|
86
|
+
typesPath: logicDir,
|
|
87
|
+
log: () => {},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const [parsedLogic] = visitProgram(program, appOptions)
|
|
91
|
+
await writeTypeImports(appOptions, program, logicPath, [parsedLogic], [parsedLogic])
|
|
92
|
+
|
|
93
|
+
const writtenLogic = fs.readFileSync(logicPath, 'utf8')
|
|
94
|
+
|
|
95
|
+
expect(writtenLogic).toMatch(/import type \{ logicType \} from ['"]\.\/logicType['"]/)
|
|
96
|
+
expect(writtenLogic).toContain('export const logic = kea<logicType>({')
|
|
97
|
+
} finally {
|
|
98
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('writeTypeImports replaces existing kea generic without leaving a trailing >', async () => {
|
|
103
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-write-existing-generic-'))
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const logicDir = path.join(tempDir, 'src')
|
|
107
|
+
const logicPath = path.join(logicDir, 'logic.ts')
|
|
108
|
+
const logicTypePath = path.join(logicDir, 'logicType.ts')
|
|
109
|
+
const keaDtsPath = path.join(tempDir, 'node_modules', 'kea', 'index.d.ts')
|
|
110
|
+
|
|
111
|
+
fs.mkdirSync(path.dirname(keaDtsPath), { recursive: true })
|
|
112
|
+
fs.mkdirSync(logicDir, { recursive: true })
|
|
113
|
+
|
|
114
|
+
fs.writeFileSync(keaDtsPath, 'export function kea<T = any>(input: any): T\n')
|
|
115
|
+
fs.writeFileSync(logicTypePath, 'export interface logicType {}\n')
|
|
116
|
+
fs.writeFileSync(
|
|
117
|
+
logicPath,
|
|
118
|
+
[
|
|
119
|
+
"import type { logicType } from './logicType'",
|
|
120
|
+
"import { kea, actions } from 'kea'",
|
|
121
|
+
'',
|
|
122
|
+
'export const logic = kea<logicType<string>>([',
|
|
123
|
+
' actions({',
|
|
124
|
+
' setValue: (value: string) => ({ value }),',
|
|
125
|
+
' }),',
|
|
126
|
+
'])',
|
|
127
|
+
'',
|
|
128
|
+
].join('\n'),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const program = ts.createProgram([logicPath], {
|
|
132
|
+
module: ts.ModuleKind.CommonJS,
|
|
133
|
+
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
134
|
+
target: ts.ScriptTarget.ES2020,
|
|
135
|
+
skipLibCheck: true,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const appOptions: AppOptions = {
|
|
139
|
+
rootPath: logicDir,
|
|
140
|
+
typesPath: logicDir,
|
|
141
|
+
log: () => {},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const [parsedLogic] = visitProgram(program, appOptions)
|
|
145
|
+
await writeTypeImports(appOptions, program, logicPath, [parsedLogic], [parsedLogic])
|
|
146
|
+
|
|
147
|
+
const writtenLogic = fs.readFileSync(logicPath, 'utf8')
|
|
148
|
+
|
|
149
|
+
expect(writtenLogic).toMatch(/import type \{ logicType \} from ['"]\.\/logicType['"]/)
|
|
150
|
+
expect(writtenLogic).toContain('export const logic = kea<logicType>([')
|
|
151
|
+
expect(writtenLogic).not.toContain('kea<logicType>>([')
|
|
152
|
+
} finally {
|
|
153
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('visitProgram limits project-aware single-file generation to the requested source file', () => {
|
|
158
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-single-file-'))
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const logicDir = path.join(tempDir, 'src')
|
|
162
|
+
const firstLogicPath = path.join(logicDir, 'firstLogic.ts')
|
|
163
|
+
const secondLogicPath = path.join(logicDir, 'secondLogic.ts')
|
|
164
|
+
const keaDtsPath = path.join(tempDir, 'node_modules', 'kea', 'index.d.ts')
|
|
165
|
+
|
|
166
|
+
fs.mkdirSync(path.dirname(keaDtsPath), { recursive: true })
|
|
167
|
+
fs.mkdirSync(logicDir, { recursive: true })
|
|
168
|
+
|
|
169
|
+
fs.writeFileSync(keaDtsPath, 'export function kea<T = any>(input: any): T\n')
|
|
170
|
+
fs.writeFileSync(
|
|
171
|
+
firstLogicPath,
|
|
172
|
+
[
|
|
173
|
+
"import { kea } from 'kea'",
|
|
174
|
+
'',
|
|
175
|
+
'export const firstLogic = kea({',
|
|
176
|
+
' actions: () => ({ first: true }),',
|
|
177
|
+
'})',
|
|
178
|
+
'',
|
|
179
|
+
].join('\n'),
|
|
180
|
+
)
|
|
181
|
+
fs.writeFileSync(
|
|
182
|
+
secondLogicPath,
|
|
183
|
+
[
|
|
184
|
+
"import { kea } from 'kea'",
|
|
185
|
+
'',
|
|
186
|
+
'export const secondLogic = kea({',
|
|
187
|
+
' actions: () => ({ second: true }),',
|
|
188
|
+
'})',
|
|
189
|
+
'',
|
|
190
|
+
].join('\n'),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
const program = ts.createProgram([firstLogicPath, secondLogicPath], {
|
|
194
|
+
module: ts.ModuleKind.CommonJS,
|
|
195
|
+
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
196
|
+
target: ts.ScriptTarget.ES2020,
|
|
197
|
+
skipLibCheck: true,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const parsedLogics = visitProgram(program, {
|
|
201
|
+
rootPath: logicDir,
|
|
202
|
+
typesPath: logicDir,
|
|
203
|
+
sourceFilePath: secondLogicPath,
|
|
204
|
+
log: () => {},
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
expect(parsedLogics.map((logic) => logic.logicName)).toEqual(['secondLogic'])
|
|
208
|
+
} finally {
|
|
209
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
210
|
+
}
|
|
211
|
+
})
|
package/src/cli/typegen.ts
CHANGED
|
@@ -18,7 +18,11 @@ yargs
|
|
|
18
18
|
await runTypeGen({ ...includeKeaConfig(parsedToAppOptions(argv)), write: true, watch: true })
|
|
19
19
|
})
|
|
20
20
|
.option('config', { alias: 'c', describe: 'Path to tsconfig.json (otherwise auto-detected)', type: 'string' })
|
|
21
|
-
.option('file', {
|
|
21
|
+
.option('file', {
|
|
22
|
+
alias: 'f',
|
|
23
|
+
describe: 'Single file to evaluate. Uses --config/.kearc compiler options when available.',
|
|
24
|
+
type: 'string',
|
|
25
|
+
})
|
|
22
26
|
.option('root', {
|
|
23
27
|
alias: 'r',
|
|
24
28
|
describe: 'Root for logic paths. E.g: ./frontend/src',
|
|
@@ -51,6 +55,11 @@ yargs
|
|
|
51
55
|
describe: 'Cache generated logic files into .typegen, use them if generating a logic type for the first time',
|
|
52
56
|
type: 'boolean',
|
|
53
57
|
})
|
|
58
|
+
.option('prettier', {
|
|
59
|
+
describe: 'Format generated logic type declarations with Prettier (use --no-prettier to skip)',
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
default: true,
|
|
62
|
+
})
|
|
54
63
|
.option('verbose', { describe: 'Slightly more verbose output log', type: 'boolean' })
|
|
55
64
|
.demandCommand()
|
|
56
65
|
.help()
|
package/src/print/print.ts
CHANGED
|
@@ -40,8 +40,16 @@ import { printInternalExtraInput } from './printInternalExtraInput'
|
|
|
40
40
|
import { convertToBuilders } from '../write/convertToBuilders'
|
|
41
41
|
import { cacheWrittenFile } from '../cache'
|
|
42
42
|
|
|
43
|
+
const prettierConfigCache = new Map<string, Promise<prettier.Options | null>>()
|
|
44
|
+
|
|
43
45
|
export async function runThroughPrettier(sourceText: string, filePath: string): Promise<string> {
|
|
44
|
-
|
|
46
|
+
let configPromise = prettierConfigCache.get(filePath)
|
|
47
|
+
if (!configPromise) {
|
|
48
|
+
configPromise = prettier.resolveConfig(filePath, { useCache: true })
|
|
49
|
+
prettierConfigCache.set(filePath, configPromise)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const options = await configPromise
|
|
45
53
|
if (options) {
|
|
46
54
|
try {
|
|
47
55
|
return await prettier.format(sourceText, { ...options, filepath: filePath })
|
|
@@ -107,11 +115,12 @@ export async function printToFiles(
|
|
|
107
115
|
const logicStrings = []
|
|
108
116
|
const requiredKeys = new Set(['Logic'])
|
|
109
117
|
for (const parsedLogic of parsedLogics) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
const logicTypeString = nodeToString(parsedLogic.interfaceDeclaration)
|
|
119
|
+
logicStrings.push(
|
|
120
|
+
appOptions.prettier === false
|
|
121
|
+
? logicTypeString
|
|
122
|
+
: await runThroughPrettier(logicTypeString, typeFileName),
|
|
113
123
|
)
|
|
114
|
-
logicStrings.push(logicTypeStirng)
|
|
115
124
|
for (const string of parsedLogic.importFromKeaInLogicType.values()) {
|
|
116
125
|
requiredKeys.add(string)
|
|
117
126
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
|
|
4
|
+
require('ts-node/register/transpile-only')
|
|
5
|
+
|
|
6
|
+
const repoRoot = path.resolve(__dirname, '..', '..')
|
|
7
|
+
const projectDir = fs.mkdtempSync(path.join(repoRoot, 'tmp-watch-smoke-'))
|
|
8
|
+
const tsconfigPath = path.join(projectDir, 'tsconfig.json')
|
|
9
|
+
const fileCount = 200
|
|
10
|
+
|
|
11
|
+
const noop = () => {}
|
|
12
|
+
console.info = noop
|
|
13
|
+
|
|
14
|
+
let active = 0
|
|
15
|
+
let maxActive = 0
|
|
16
|
+
let started = 0
|
|
17
|
+
let completed = 0
|
|
18
|
+
let settleTimer
|
|
19
|
+
let timeoutTimer
|
|
20
|
+
let finished = false
|
|
21
|
+
|
|
22
|
+
function cleanup() {
|
|
23
|
+
try {
|
|
24
|
+
fs.rmSync(projectDir, { recursive: true, force: true })
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function finish(code) {
|
|
29
|
+
if (finished) {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
finished = true
|
|
33
|
+
clearTimeout(settleTimer)
|
|
34
|
+
clearTimeout(timeoutTimer)
|
|
35
|
+
cleanup()
|
|
36
|
+
|
|
37
|
+
fs.writeFileSync(
|
|
38
|
+
process.stdout.fd,
|
|
39
|
+
JSON.stringify({
|
|
40
|
+
started,
|
|
41
|
+
completed,
|
|
42
|
+
active,
|
|
43
|
+
maxActive,
|
|
44
|
+
}) + '\n',
|
|
45
|
+
)
|
|
46
|
+
process.exit(code)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function scheduleFinishIfSettled() {
|
|
50
|
+
if (started > 1 && active === 0) {
|
|
51
|
+
clearTimeout(settleTimer)
|
|
52
|
+
settleTimer = setTimeout(() => finish(0), 300)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
process.on('uncaughtException', (error) => {
|
|
57
|
+
console.error(error)
|
|
58
|
+
finish(1)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
process.on('unhandledRejection', (error) => {
|
|
62
|
+
console.error(error)
|
|
63
|
+
finish(1)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
fs.writeFileSync(
|
|
67
|
+
tsconfigPath,
|
|
68
|
+
JSON.stringify(
|
|
69
|
+
{
|
|
70
|
+
compilerOptions: {
|
|
71
|
+
target: 'ES2020',
|
|
72
|
+
module: 'commonjs',
|
|
73
|
+
moduleResolution: 'node',
|
|
74
|
+
esModuleInterop: true,
|
|
75
|
+
skipLibCheck: true,
|
|
76
|
+
strict: false,
|
|
77
|
+
},
|
|
78
|
+
include: ['src/**/*'],
|
|
79
|
+
},
|
|
80
|
+
null,
|
|
81
|
+
2,
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < fileCount; i++) {
|
|
86
|
+
const dir = path.join(projectDir, 'src', `group${String(i % 10).padStart(2, '0')}`)
|
|
87
|
+
const filePath = path.join(dir, `logic${i}.ts`)
|
|
88
|
+
|
|
89
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
90
|
+
fs.writeFileSync(
|
|
91
|
+
filePath,
|
|
92
|
+
[
|
|
93
|
+
"import { kea } from 'kea'",
|
|
94
|
+
'',
|
|
95
|
+
`export const logic${i} = kea({`,
|
|
96
|
+
' actions: () => ({',
|
|
97
|
+
' setValue: (value: string) => ({ value }),',
|
|
98
|
+
' }),',
|
|
99
|
+
' reducers: () => ({',
|
|
100
|
+
" value: ['' as string, { setValue: (_, payload) => payload.value }],",
|
|
101
|
+
' }),',
|
|
102
|
+
'})',
|
|
103
|
+
'',
|
|
104
|
+
].join('\n'),
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const printModule = require(path.join(repoRoot, 'src/print/print'))
|
|
109
|
+
const originalPrintToFiles = printModule.printToFiles
|
|
110
|
+
|
|
111
|
+
printModule.printToFiles = async function (...args) {
|
|
112
|
+
active += 1
|
|
113
|
+
started += 1
|
|
114
|
+
maxActive = Math.max(maxActive, active)
|
|
115
|
+
clearTimeout(settleTimer)
|
|
116
|
+
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
return await originalPrintToFiles.apply(this, args)
|
|
121
|
+
} finally {
|
|
122
|
+
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
123
|
+
active -= 1
|
|
124
|
+
completed += 1
|
|
125
|
+
scheduleFinishIfSettled()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { runTypeGen } = require(path.join(repoRoot, 'src/typegen'))
|
|
130
|
+
|
|
131
|
+
timeoutTimer = setTimeout(() => finish(1), 20000)
|
|
132
|
+
|
|
133
|
+
runTypeGen({
|
|
134
|
+
tsConfigPath: tsconfigPath,
|
|
135
|
+
rootPath: projectDir,
|
|
136
|
+
typesPath: projectDir,
|
|
137
|
+
write: true,
|
|
138
|
+
watch: true,
|
|
139
|
+
log: noop,
|
|
140
|
+
})
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const Module = require('module')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
require('ts-node/register/transpile-only')
|
|
6
|
+
|
|
7
|
+
const repoRoot = path.resolve(__dirname, '..', '..')
|
|
8
|
+
const projectDir = fs.mkdtempSync(path.join(repoRoot, 'tmp-write-smoke-'))
|
|
9
|
+
const tsconfigPath = path.join(projectDir, 'tsconfig.json')
|
|
10
|
+
const logicFilePath = path.join(projectDir, 'src', 'logic.ts')
|
|
11
|
+
|
|
12
|
+
const noop = () => {}
|
|
13
|
+
|
|
14
|
+
let createProgramCalls = 0
|
|
15
|
+
let reusedOldProgram = false
|
|
16
|
+
let finished = false
|
|
17
|
+
const exitSignalKey = '__writeModeSmokeExit'
|
|
18
|
+
|
|
19
|
+
const originalTs = require('typescript')
|
|
20
|
+
const instrumentedTs = new Proxy(originalTs, {
|
|
21
|
+
get(target, property, receiver) {
|
|
22
|
+
if (property === 'createProgram') {
|
|
23
|
+
return function (...args) {
|
|
24
|
+
createProgramCalls += 1
|
|
25
|
+
reusedOldProgram = reusedOldProgram || !!args[3]
|
|
26
|
+
return Reflect.get(target, property, receiver).apply(this, args)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return Reflect.get(target, property, receiver)
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const originalModuleLoad = Module._load
|
|
35
|
+
Module._load = function (request, parent, isMain) {
|
|
36
|
+
if (request === 'typescript') {
|
|
37
|
+
return instrumentedTs
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return originalModuleLoad.apply(this, arguments)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function cleanup() {
|
|
44
|
+
try {
|
|
45
|
+
fs.rmSync(projectDir, { recursive: true, force: true })
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function finish(code, error) {
|
|
50
|
+
if (finished) {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
finished = true
|
|
54
|
+
|
|
55
|
+
cleanup()
|
|
56
|
+
|
|
57
|
+
if (error) {
|
|
58
|
+
console.error(error)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fs.writeFileSync(
|
|
62
|
+
process.stdout.fd,
|
|
63
|
+
JSON.stringify({
|
|
64
|
+
createProgramCalls,
|
|
65
|
+
reusedOldProgram,
|
|
66
|
+
}) + '\n',
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
process.exitCode = code
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const originalProcessExit = process.exit
|
|
73
|
+
process.exit = (code) => {
|
|
74
|
+
throw { [exitSignalKey]: true, code: code ?? 0 }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
process.on('uncaughtException', (error) => {
|
|
78
|
+
finish(1, error)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
process.on('unhandledRejection', (error) => {
|
|
82
|
+
finish(1, error)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
fs.mkdirSync(path.dirname(logicFilePath), { recursive: true })
|
|
86
|
+
fs.writeFileSync(
|
|
87
|
+
tsconfigPath,
|
|
88
|
+
JSON.stringify(
|
|
89
|
+
{
|
|
90
|
+
compilerOptions: {
|
|
91
|
+
target: 'ES2020',
|
|
92
|
+
module: 'commonjs',
|
|
93
|
+
moduleResolution: 'node',
|
|
94
|
+
esModuleInterop: true,
|
|
95
|
+
skipLibCheck: true,
|
|
96
|
+
strict: false,
|
|
97
|
+
},
|
|
98
|
+
include: ['src/**/*'],
|
|
99
|
+
},
|
|
100
|
+
null,
|
|
101
|
+
2,
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
fs.writeFileSync(
|
|
106
|
+
logicFilePath,
|
|
107
|
+
[
|
|
108
|
+
"import { kea } from 'kea'",
|
|
109
|
+
'',
|
|
110
|
+
'export const logic = kea({',
|
|
111
|
+
' actions: () => ({',
|
|
112
|
+
' setValue: (value: string) => ({ value }),',
|
|
113
|
+
' }),',
|
|
114
|
+
' reducers: () => ({',
|
|
115
|
+
" value: ['', { setValue: (_, payload) => payload.value }],",
|
|
116
|
+
' }),',
|
|
117
|
+
'})',
|
|
118
|
+
'',
|
|
119
|
+
].join('\n'),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
const { runTypeGen } = require(path.join(repoRoot, 'src/typegen'))
|
|
123
|
+
|
|
124
|
+
Promise.resolve(
|
|
125
|
+
runTypeGen({
|
|
126
|
+
tsConfigPath: tsconfigPath,
|
|
127
|
+
rootPath: projectDir,
|
|
128
|
+
typesPath: projectDir,
|
|
129
|
+
write: true,
|
|
130
|
+
watch: false,
|
|
131
|
+
log: noop,
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
134
|
+
.then(() => {
|
|
135
|
+
finish(0)
|
|
136
|
+
})
|
|
137
|
+
.catch((error) => {
|
|
138
|
+
if (error && error[exitSignalKey]) {
|
|
139
|
+
finish(error.code)
|
|
140
|
+
} else {
|
|
141
|
+
finish(1, error)
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
.finally(() => {
|
|
145
|
+
Module._load = originalModuleLoad
|
|
146
|
+
process.exit = originalProcessExit
|
|
147
|
+
})
|
package/src/typegen.ts
CHANGED
|
@@ -25,21 +25,26 @@ export async function runTypeGen(appOptions: AppOptions) {
|
|
|
25
25
|
|
|
26
26
|
if (appOptions.sourceFilePath) {
|
|
27
27
|
log(`❇️ Loading file: ${appOptions.sourceFilePath}`)
|
|
28
|
+
const compilerOptions = appOptions.tsConfigPath
|
|
29
|
+
? loadTsConfig(appOptions.tsConfigPath, log).options
|
|
30
|
+
: {
|
|
31
|
+
target: ts.ScriptTarget.ES5,
|
|
32
|
+
module: ts.ModuleKind.CommonJS,
|
|
33
|
+
noEmit: true,
|
|
34
|
+
noErrorTruncation: true,
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
resetProgram = () => {
|
|
29
|
-
program =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
program = replaceProgram(
|
|
39
|
+
() => ts.createProgram([appOptions.sourceFilePath], compilerOptions),
|
|
40
|
+
(nextProgram) => {
|
|
41
|
+
program = nextProgram
|
|
42
|
+
},
|
|
43
|
+
)
|
|
35
44
|
}
|
|
36
45
|
resetProgram()
|
|
37
46
|
} else if (appOptions.tsConfigPath) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const configFile = ts.readJsonConfigFile(appOptions.tsConfigPath, ts.sys.readFile)
|
|
41
|
-
const rootFolder = path.dirname(appOptions.tsConfigPath)
|
|
42
|
-
const compilerOptions = ts.parseJsonSourceFileConfigFileContent(configFile, ts.sys, rootFolder)
|
|
47
|
+
const compilerOptions = loadTsConfig(appOptions.tsConfigPath, log)
|
|
43
48
|
|
|
44
49
|
if (appOptions.watch) {
|
|
45
50
|
// We don't emit JavaScript files in typegen watch mode, so the semantic-only
|
|
@@ -85,24 +90,62 @@ export async function runTypeGen(appOptions: AppOptions) {
|
|
|
85
90
|
console.info(codes[diagnostic.code] || `🥚 ${ts.formatDiagnostic(diagnostic, formatHost).trim()}`)
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
const origCreateProgram = host.createProgram
|
|
89
|
-
host.createProgram = (rootNames: ReadonlyArray<string>, options, host, oldProgram) => {
|
|
90
|
-
return origCreateProgram(rootNames, options, host, oldProgram)
|
|
91
|
-
}
|
|
92
93
|
const origPostProgramCreate = host.afterProgramCreate
|
|
94
|
+
let scheduledProgram: Program | undefined
|
|
95
|
+
let runningTypegen = false
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
const runScheduledTypegen = async () => {
|
|
98
|
+
if (runningTypegen) {
|
|
99
|
+
return
|
|
100
|
+
}
|
|
97
101
|
|
|
98
|
-
|
|
102
|
+
runningTypegen = true
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
while (scheduledProgram) {
|
|
106
|
+
const nextProgram = scheduledProgram
|
|
107
|
+
scheduledProgram = undefined
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
await goThroughAllTheFiles(nextProgram, appOptions)
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('⛔ Error running kea-typegen in watch mode')
|
|
113
|
+
console.error(error)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
runningTypegen = false
|
|
118
|
+
|
|
119
|
+
if (scheduledProgram) {
|
|
120
|
+
void runScheduledTypegen()
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
host.afterProgramCreate = (prog) => {
|
|
126
|
+
program = prog.getProgram()
|
|
127
|
+
origPostProgramCreate?.(prog)
|
|
128
|
+
scheduledProgram = program
|
|
129
|
+
void runScheduledTypegen()
|
|
99
130
|
}
|
|
100
131
|
|
|
101
132
|
ts.createWatchProgram(host)
|
|
102
133
|
} else {
|
|
103
|
-
const host = ts.createCompilerHost(compilerOptions.options)
|
|
104
134
|
resetProgram = () => {
|
|
105
|
-
|
|
135
|
+
// Write mode can require multiple passes as generated imports and type files
|
|
136
|
+
// land on disk. Reusing the previous Program retains too much compiler state
|
|
137
|
+
// for large projects, so rebuild from a fresh host each round instead.
|
|
138
|
+
// Clear the previous Program reference before allocating the next one to
|
|
139
|
+
// avoid holding both huge compiler graphs live at the same time.
|
|
140
|
+
program = replaceProgram(
|
|
141
|
+
() => {
|
|
142
|
+
const host = ts.createCompilerHost(compilerOptions.options)
|
|
143
|
+
return ts.createProgram(compilerOptions.fileNames, compilerOptions.options, host)
|
|
144
|
+
},
|
|
145
|
+
(nextProgram) => {
|
|
146
|
+
program = nextProgram
|
|
147
|
+
},
|
|
148
|
+
)
|
|
106
149
|
}
|
|
107
150
|
resetProgram()
|
|
108
151
|
}
|
|
@@ -130,7 +173,12 @@ export async function runTypeGen(appOptions: AppOptions) {
|
|
|
130
173
|
return response
|
|
131
174
|
}
|
|
132
175
|
|
|
133
|
-
if (program && !appOptions.watch &&
|
|
176
|
+
if (program && !appOptions.watch && appOptions.sourceFilePath) {
|
|
177
|
+
await goThroughAllTheFiles(program, appOptions)
|
|
178
|
+
if (appOptions.write) {
|
|
179
|
+
log(`👋 Finished writing files! Exiting.`)
|
|
180
|
+
}
|
|
181
|
+
} else if (program && !appOptions.watch) {
|
|
134
182
|
if (appOptions.write) {
|
|
135
183
|
if (restoreCachedTypes(program, appOptions, log)) {
|
|
136
184
|
resetProgram()
|
|
@@ -158,3 +206,17 @@ export async function runTypeGen(appOptions: AppOptions) {
|
|
|
158
206
|
}
|
|
159
207
|
}
|
|
160
208
|
}
|
|
209
|
+
|
|
210
|
+
function loadTsConfig(tsConfigPath: string, log: AppOptions['log']): ts.ParsedCommandLine {
|
|
211
|
+
log(`🥚 TypeScript Config: ${tsConfigPath}`)
|
|
212
|
+
const configFile = ts.readJsonConfigFile(tsConfigPath, ts.sys.readFile)
|
|
213
|
+
const rootFolder = path.dirname(tsConfigPath)
|
|
214
|
+
return ts.parseJsonSourceFileConfigFileContent(configFile, ts.sys, rootFolder)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function replaceProgram(createProgram: () => Program, setProgram: (program?: Program) => void): Program {
|
|
218
|
+
setProgram(undefined)
|
|
219
|
+
const nextProgram = createProgram()
|
|
220
|
+
setProgram(nextProgram)
|
|
221
|
+
return nextProgram
|
|
222
|
+
}
|