kea-typegen 3.8.1 → 3.8.3
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/README.md +3 -2
- package/dist/package.json +1 -1
- package/dist/src/__tests__/inline.js +149 -10
- package/dist/src/__tests__/inline.js.map +1 -1
- package/dist/src/print/print.js +8 -3
- package/dist/src/print/print.js.map +1 -1
- package/dist/src/utils.js +1 -1
- package/dist/src/utils.js.map +1 -1
- package/dist/src/write/writeInlineLogicTypes.d.ts +1 -1
- package/dist/src/write/writeInlineLogicTypes.js +79 -15
- package/dist/src/write/writeInlineLogicTypes.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/samples-inline/counterLogic.ts +2 -2
- package/samples-inline/profileLogic.ts +2 -2
- package/samples-inline/searchLogic.ts +3 -3
- package/src/__tests__/inline.ts +231 -22
- package/src/print/print.ts +11 -3
- package/src/utils.ts +2 -2
- package/src/write/writeInlineLogicTypes.ts +109 -27
package/src/__tests__/inline.ts
CHANGED
|
@@ -5,7 +5,37 @@ import * as ts from 'typescript'
|
|
|
5
5
|
import { AppOptions } from '../types'
|
|
6
6
|
import { programFromSource } from '../utils'
|
|
7
7
|
import { visitProgram } from '../visit/visit'
|
|
8
|
-
import { printToFiles } from '../print/print'
|
|
8
|
+
import { buildTypeImportEntries, printToFiles } from '../print/print'
|
|
9
|
+
|
|
10
|
+
describe('inline type imports', () => {
|
|
11
|
+
test('collapses physical pnpm paths outside the configured node_modules root', () => {
|
|
12
|
+
const workspace = path.join(os.tmpdir(), 'kea-typegen-workspace')
|
|
13
|
+
const physicalType = path.join(
|
|
14
|
+
workspace,
|
|
15
|
+
'node_modules',
|
|
16
|
+
'.pnpm',
|
|
17
|
+
'kea-router@3.4.1_kea@4.0.0_patch_hash=abc',
|
|
18
|
+
'node_modules',
|
|
19
|
+
'kea-router',
|
|
20
|
+
'lib',
|
|
21
|
+
'types.d.ts',
|
|
22
|
+
)
|
|
23
|
+
const entries = buildTypeImportEntries(
|
|
24
|
+
{ [physicalType]: new Set(['LocationChangedPayload']) },
|
|
25
|
+
path.join(workspace, 'frontend', 'src', 'logic.ts'),
|
|
26
|
+
path.join(workspace, 'frontend', 'node_modules'),
|
|
27
|
+
() => false,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
expect(entries).toEqual([
|
|
31
|
+
{
|
|
32
|
+
list: ['LocationChangedPayload'],
|
|
33
|
+
fullPath: physicalType,
|
|
34
|
+
finalPath: 'kea-router/lib/types',
|
|
35
|
+
},
|
|
36
|
+
])
|
|
37
|
+
})
|
|
38
|
+
})
|
|
9
39
|
|
|
10
40
|
describe('manually typed logics', () => {
|
|
11
41
|
test('skips logics typed with MakeLogicType', () => {
|
|
@@ -117,7 +147,8 @@ describe('inline mode', () => {
|
|
|
117
147
|
keaDtsPath,
|
|
118
148
|
[
|
|
119
149
|
'export function kea<T = any>(input: any): T',
|
|
120
|
-
'export interface MakeLogicType<V = any, A = any, P = any> {}',
|
|
150
|
+
'export interface MakeLogicType<V = any, A = any, P = any, M = any> {}',
|
|
151
|
+
'export type BreakPointFunction = (() => void) & ((ms: number) => Promise<void>)',
|
|
121
152
|
'',
|
|
122
153
|
].join('\n'),
|
|
123
154
|
)
|
|
@@ -128,12 +159,16 @@ describe('inline mode', () => {
|
|
|
128
159
|
'',
|
|
129
160
|
'export const logic = kea({',
|
|
130
161
|
' props: {} as { id: number },',
|
|
162
|
+
' key: (props: { id: number }) => props.id,',
|
|
131
163
|
' actions: () => ({',
|
|
132
164
|
' setValue: (value: string) => ({ value }),',
|
|
133
165
|
' }),',
|
|
134
166
|
' reducers: () => ({',
|
|
135
167
|
" value: ['' as string, { setValue: (_, { value }) => value }],",
|
|
136
168
|
' }),',
|
|
169
|
+
' sharedListeners: () => ({',
|
|
170
|
+
' syncValue: () => {},',
|
|
171
|
+
' }),',
|
|
137
172
|
'})',
|
|
138
173
|
'',
|
|
139
174
|
].join('\n'),
|
|
@@ -158,31 +193,44 @@ describe('inline mode', () => {
|
|
|
158
193
|
const writtenLogic = fs.readFileSync(logicPath, 'utf8')
|
|
159
194
|
|
|
160
195
|
expect(writtenLogic).toContain("import { MakeLogicType, kea } from 'kea'")
|
|
161
|
-
expect(writtenLogic).toContain(
|
|
196
|
+
expect(writtenLogic).toContain(
|
|
197
|
+
"// Generated by kea-typegen. Update if you're an agent, ignore if you're human.",
|
|
198
|
+
)
|
|
162
199
|
expect(writtenLogic).toContain('export interface logicValues {')
|
|
163
200
|
expect(writtenLogic).toContain('export interface logicActions {')
|
|
164
201
|
expect(writtenLogic).toContain('export interface logicProps {')
|
|
165
|
-
expect(writtenLogic).toContain('export
|
|
202
|
+
expect(writtenLogic).toContain('export interface logicMeta {')
|
|
203
|
+
expect(writtenLogic).toContain('key: number')
|
|
204
|
+
expect(writtenLogic).toContain('sharedListeners: {')
|
|
205
|
+
expect(writtenLogic).toContain(
|
|
206
|
+
'export type logicType = MakeLogicType<logicValues, logicActions, logicProps, logicMeta>',
|
|
207
|
+
)
|
|
208
|
+
expect(writtenLogic).toContain("import type { BreakPointFunction } from 'kea'")
|
|
166
209
|
expect(writtenLogic).toContain('export const logic = kea<logicType>({')
|
|
167
210
|
expect(writtenLogic).toContain('value: string')
|
|
168
211
|
expect(writtenLogic).toContain('setValue: (value: string) =>')
|
|
169
212
|
expect(fs.existsSync(logicTypePath)).toBe(false)
|
|
170
213
|
|
|
171
|
-
// a second pass
|
|
214
|
+
// a second pass leaves formatter-unquoted property names alone
|
|
215
|
+
const formattedLogic = writtenLogic.replace('"syncValue":', 'syncValue:')
|
|
216
|
+
fs.writeFileSync(logicPath, formattedLogic)
|
|
172
217
|
const secondProgram = createProgram([logicPath])
|
|
173
218
|
const secondParsedLogics = visitProgram(secondProgram, appOptions)
|
|
174
219
|
expect(secondParsedLogics.length).toBe(1)
|
|
175
220
|
|
|
176
221
|
const secondResponse = await printToFiles(secondProgram, appOptions, secondParsedLogics)
|
|
177
222
|
expect(secondResponse).toEqual({ filesToWrite: 0, writtenFiles: 0, filesToModify: 0 })
|
|
178
|
-
expect(fs.readFileSync(logicPath, 'utf8')).toBe(
|
|
223
|
+
expect(fs.readFileSync(logicPath, 'utf8')).toBe(formattedLogic)
|
|
179
224
|
|
|
180
225
|
// adding an action updates the existing block in place
|
|
181
226
|
fs.writeFileSync(
|
|
182
227
|
logicPath,
|
|
183
228
|
fs
|
|
184
229
|
.readFileSync(logicPath, 'utf8')
|
|
185
|
-
.replace(
|
|
230
|
+
.replace(
|
|
231
|
+
'setValue: (value: string) => ({ value }),',
|
|
232
|
+
'setValue: (value: string) => ({ value }),\n reset: true,',
|
|
233
|
+
),
|
|
186
234
|
)
|
|
187
235
|
|
|
188
236
|
const thirdProgram = createProgram([logicPath])
|
|
@@ -194,8 +242,8 @@ describe('inline mode', () => {
|
|
|
194
242
|
expect(updatedLogic.match(/export type logicType = MakeLogicType</g)?.length).toBe(1)
|
|
195
243
|
expect(updatedLogic.match(/export interface logicValues \{/g)?.length).toBe(1)
|
|
196
244
|
expect(updatedLogic.match(/export interface logicActions \{/g)?.length).toBe(1)
|
|
197
|
-
// one marker per generated interface (values, actions, props)
|
|
198
|
-
expect(updatedLogic.match(/
|
|
245
|
+
// one marker per generated interface (values, actions, props, meta)
|
|
246
|
+
expect(updatedLogic.match(/Update if you're an agent, ignore if you're human/g)?.length).toBe(4)
|
|
199
247
|
} finally {
|
|
200
248
|
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
201
249
|
}
|
|
@@ -310,15 +358,31 @@ describe('inline mode', () => {
|
|
|
310
358
|
const program = createProgram([logicPath])
|
|
311
359
|
await printToFiles(program, appOptions, visitProgram(program, appOptions))
|
|
312
360
|
|
|
313
|
-
// simulate
|
|
361
|
+
// simulate an oxfmt pass: strip semicolons and reflow nested unions and objects
|
|
314
362
|
const formatted = fs
|
|
315
363
|
.readFileSync(logicPath, 'utf8')
|
|
316
364
|
.replace(/;$/gm, '')
|
|
317
|
-
.replace(
|
|
365
|
+
.replace(
|
|
366
|
+
/setValue: \(value: string \| number\) => \{\n value: string \| number\n \}/g,
|
|
367
|
+
[
|
|
368
|
+
'setValue: (',
|
|
369
|
+
' value:',
|
|
370
|
+
' | string',
|
|
371
|
+
' | number,',
|
|
372
|
+
' ) => {',
|
|
373
|
+
' value: string | number',
|
|
374
|
+
' }',
|
|
375
|
+
].join('\n'),
|
|
376
|
+
)
|
|
377
|
+
.replace(' value: string | number\n }', ' value: (string | number)\n }')
|
|
318
378
|
fs.writeFileSync(logicPath, formatted)
|
|
319
379
|
|
|
320
380
|
const secondProgram = createProgram([logicPath])
|
|
321
|
-
const secondResponse = await printToFiles(
|
|
381
|
+
const secondResponse = await printToFiles(
|
|
382
|
+
secondProgram,
|
|
383
|
+
appOptions,
|
|
384
|
+
visitProgram(secondProgram, appOptions),
|
|
385
|
+
)
|
|
322
386
|
expect(secondResponse).toEqual({ filesToWrite: 0, writtenFiles: 0, filesToModify: 0 })
|
|
323
387
|
expect(fs.readFileSync(logicPath, 'utf8')).toBe(formatted)
|
|
324
388
|
} finally {
|
|
@@ -381,7 +445,7 @@ describe('inline mode', () => {
|
|
|
381
445
|
].join('\n'),
|
|
382
446
|
)
|
|
383
447
|
// the generated block sits above the comment
|
|
384
|
-
expect(writtenLogic.indexOf('
|
|
448
|
+
expect(writtenLogic.indexOf("Update if you're an agent, ignore if you're human")).toBeLessThan(
|
|
385
449
|
writtenLogic.indexOf('// this comment describes the logic'),
|
|
386
450
|
)
|
|
387
451
|
} finally {
|
|
@@ -449,7 +513,11 @@ describe('inline mode', () => {
|
|
|
449
513
|
|
|
450
514
|
// a second pass changes nothing
|
|
451
515
|
const secondProgram = createProgram([logicPath])
|
|
452
|
-
const secondResponse = await printToFiles(
|
|
516
|
+
const secondResponse = await printToFiles(
|
|
517
|
+
secondProgram,
|
|
518
|
+
appOptions,
|
|
519
|
+
visitProgram(secondProgram, appOptions),
|
|
520
|
+
)
|
|
453
521
|
expect(secondResponse).toEqual({ filesToWrite: 0, writtenFiles: 0, filesToModify: 0 })
|
|
454
522
|
} finally {
|
|
455
523
|
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
@@ -503,8 +571,14 @@ describe('mixed mode with inlinePaths', () => {
|
|
|
503
571
|
writeKeaDts(tempDir)
|
|
504
572
|
fs.mkdirSync(inlineDir, { recursive: true })
|
|
505
573
|
fs.mkdirSync(classicDir, { recursive: true })
|
|
506
|
-
fs.writeFileSync(
|
|
507
|
-
|
|
574
|
+
fs.writeFileSync(
|
|
575
|
+
inlineLogicPath,
|
|
576
|
+
simpleLogicSource.replace('export const logic', 'export const inlinedLogic'),
|
|
577
|
+
)
|
|
578
|
+
fs.writeFileSync(
|
|
579
|
+
classicLogicPath,
|
|
580
|
+
simpleLogicSource.replace('export const logic', 'export const classicLogic'),
|
|
581
|
+
)
|
|
508
582
|
|
|
509
583
|
const appOptions: AppOptions = {
|
|
510
584
|
rootPath: rootDir,
|
|
@@ -520,8 +594,12 @@ describe('mixed mode with inlinePaths', () => {
|
|
|
520
594
|
await printToFiles(program, appOptions, visitProgram(program, appOptions))
|
|
521
595
|
|
|
522
596
|
const writtenInlineLogic = fs.readFileSync(inlineLogicPath, 'utf8')
|
|
523
|
-
expect(writtenInlineLogic).toContain(
|
|
524
|
-
|
|
597
|
+
expect(writtenInlineLogic).toContain(
|
|
598
|
+
"// Generated by kea-typegen. Update if you're an agent, ignore if you're human.",
|
|
599
|
+
)
|
|
600
|
+
expect(writtenInlineLogic).toContain(
|
|
601
|
+
'export type inlinedLogicType = MakeLogicType<{}, inlinedLogicActions>',
|
|
602
|
+
)
|
|
525
603
|
expect(fs.existsSync(path.join(inlineDir, 'inlinedLogicType.ts'))).toBe(false)
|
|
526
604
|
|
|
527
605
|
const writtenClassicLogic = fs.readFileSync(classicLogicPath, 'utf8')
|
|
@@ -542,7 +620,10 @@ describe('mixed mode with inlinePaths', () => {
|
|
|
542
620
|
|
|
543
621
|
writeKeaDts(tempDir)
|
|
544
622
|
fs.mkdirSync(inlineDir, { recursive: true })
|
|
545
|
-
fs.writeFileSync(
|
|
623
|
+
fs.writeFileSync(
|
|
624
|
+
inlineLogicPath,
|
|
625
|
+
simpleLogicSource.replace('export const logic', 'export const inlinedLogic'),
|
|
626
|
+
)
|
|
546
627
|
|
|
547
628
|
const appOptions: AppOptions = {
|
|
548
629
|
rootPath: rootDir,
|
|
@@ -557,7 +638,7 @@ describe('mixed mode with inlinePaths', () => {
|
|
|
557
638
|
await printToFiles(program, appOptions, visitProgram(program, appOptions))
|
|
558
639
|
|
|
559
640
|
expect(fs.readFileSync(inlineLogicPath, 'utf8')).toContain(
|
|
560
|
-
|
|
641
|
+
"// Generated by kea-typegen. Update if you're an agent, ignore if you're human.",
|
|
561
642
|
)
|
|
562
643
|
expect(fs.existsSync(path.join(inlineDir, 'inlinedLogicType.ts'))).toBe(false)
|
|
563
644
|
} finally {
|
|
@@ -683,13 +764,141 @@ describe('mixed mode with inlinePaths', () => {
|
|
|
683
764
|
|
|
684
765
|
// a second pass over the annotated block changes nothing
|
|
685
766
|
const secondProgram = createProgram([inlineLogicPath, consumerLogicPath, inlineConsumerLogicPath])
|
|
686
|
-
const secondResponse = await printToFiles(
|
|
767
|
+
const secondResponse = await printToFiles(
|
|
768
|
+
secondProgram,
|
|
769
|
+
appOptions,
|
|
770
|
+
visitProgram(secondProgram, appOptions),
|
|
771
|
+
)
|
|
687
772
|
expect(secondResponse).toEqual({ filesToWrite: 0, writtenFiles: 0, filesToModify: 0 })
|
|
688
773
|
} finally {
|
|
689
774
|
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
690
775
|
}
|
|
691
776
|
})
|
|
692
777
|
|
|
778
|
+
test('sorts values and actions: connected first grouped by source logic, then own, all alphabetical', async () => {
|
|
779
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-sort-'))
|
|
780
|
+
|
|
781
|
+
try {
|
|
782
|
+
const rootDir = path.join(tempDir, 'src')
|
|
783
|
+
const keaDtsPath = path.join(tempDir, 'node_modules', 'kea', 'index.d.ts')
|
|
784
|
+
fs.mkdirSync(path.dirname(keaDtsPath), { recursive: true })
|
|
785
|
+
fs.mkdirSync(rootDir, { recursive: true })
|
|
786
|
+
fs.writeFileSync(
|
|
787
|
+
keaDtsPath,
|
|
788
|
+
[
|
|
789
|
+
'export function kea<T = any>(input: any): T',
|
|
790
|
+
'export type AnyFunction = (...args: any) => any',
|
|
791
|
+
'export interface MakeLogicType<V = any, A = any, P = any> {',
|
|
792
|
+
' actionCreators: {',
|
|
793
|
+
' [K in keyof A]: A[K] extends AnyFunction',
|
|
794
|
+
' ? (...args: Parameters<A[K]>) => { type: string; payload: ReturnType<A[K]> }',
|
|
795
|
+
' : never',
|
|
796
|
+
' }',
|
|
797
|
+
' values: V',
|
|
798
|
+
'}',
|
|
799
|
+
'',
|
|
800
|
+
].join('\n'),
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
const makeProvider = (name: string, entries: [string, string][]): string =>
|
|
804
|
+
[
|
|
805
|
+
"import { MakeLogicType, kea } from 'kea'",
|
|
806
|
+
'',
|
|
807
|
+
'// Generated by kea-typegen. DO NOT EDIT THIS BLOCK MANUALLY.',
|
|
808
|
+
`export interface ${name}Values {`,
|
|
809
|
+
...entries.map(([value]) => ` ${value}: string`),
|
|
810
|
+
'}',
|
|
811
|
+
'',
|
|
812
|
+
`export interface ${name}Actions {`,
|
|
813
|
+
...entries.map(([value, action]) => ` ${action}: (${value}: string) => { ${value}: string }`),
|
|
814
|
+
'}',
|
|
815
|
+
'',
|
|
816
|
+
`export type ${name}Type = MakeLogicType<${name}Values, ${name}Actions>`,
|
|
817
|
+
'',
|
|
818
|
+
`export const ${name} = kea<${name}Type>({`,
|
|
819
|
+
' actions: () => ({',
|
|
820
|
+
...entries.map(([value, action]) => ` ${action}: (${value}: string) => ({ ${value} }),`),
|
|
821
|
+
' }),',
|
|
822
|
+
' reducers: () => ({',
|
|
823
|
+
...entries.map(([value, action]) => ` ${value}: ['', { ${action}: (_, p) => p.${value} }],`),
|
|
824
|
+
' }),',
|
|
825
|
+
'})',
|
|
826
|
+
'',
|
|
827
|
+
].join('\n')
|
|
828
|
+
|
|
829
|
+
fs.writeFileSync(
|
|
830
|
+
path.join(rootDir, 'alphaLogic.ts'),
|
|
831
|
+
makeProvider('alphaLogic', [
|
|
832
|
+
['bravo', 'setBravo'],
|
|
833
|
+
['alpha', 'setAlpha'],
|
|
834
|
+
]),
|
|
835
|
+
)
|
|
836
|
+
fs.writeFileSync(path.join(rootDir, 'betaLogic.ts'), makeProvider('betaLogic', [['charlie', 'setCharlie']]))
|
|
837
|
+
|
|
838
|
+
const consumerDir = path.join(rootDir, 'consumer')
|
|
839
|
+
const consumerLogicPath = path.join(consumerDir, 'sortedLogic.ts')
|
|
840
|
+
fs.mkdirSync(consumerDir, { recursive: true })
|
|
841
|
+
fs.writeFileSync(
|
|
842
|
+
consumerLogicPath,
|
|
843
|
+
[
|
|
844
|
+
"import { kea } from 'kea'",
|
|
845
|
+
'',
|
|
846
|
+
"import { alphaLogic } from '../alphaLogic'",
|
|
847
|
+
"import { betaLogic } from '../betaLogic'",
|
|
848
|
+
'',
|
|
849
|
+
'export const sortedLogic = kea({',
|
|
850
|
+
' connect: {',
|
|
851
|
+
" actions: [betaLogic, ['setCharlie'], alphaLogic, ['setBravo', 'setAlpha']],",
|
|
852
|
+
" values: [betaLogic, ['charlie'], alphaLogic, ['bravo', 'alpha']],",
|
|
853
|
+
' },',
|
|
854
|
+
' actions: () => ({',
|
|
855
|
+
' zulu: (z: number) => ({ z }),',
|
|
856
|
+
' aardvark: (a: number) => ({ a }),',
|
|
857
|
+
' }),',
|
|
858
|
+
' reducers: () => ({',
|
|
859
|
+
' zebra: [0, { zulu: (_, { z }) => z }],',
|
|
860
|
+
' anteater: [0, { aardvark: (_, { a }) => a }],',
|
|
861
|
+
' }),',
|
|
862
|
+
'})',
|
|
863
|
+
'',
|
|
864
|
+
].join('\n'),
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
const appOptions: AppOptions = {
|
|
868
|
+
rootPath: rootDir,
|
|
869
|
+
typesPath: rootDir,
|
|
870
|
+
packageJsonPath: path.join(tempDir, 'package.json'),
|
|
871
|
+
inlinePaths: [consumerDir],
|
|
872
|
+
write: true,
|
|
873
|
+
log: () => {},
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const program = createProgram([
|
|
877
|
+
path.join(rootDir, 'alphaLogic.ts'),
|
|
878
|
+
path.join(rootDir, 'betaLogic.ts'),
|
|
879
|
+
consumerLogicPath,
|
|
880
|
+
])
|
|
881
|
+
await printToFiles(program, appOptions, visitProgram(program, appOptions))
|
|
882
|
+
|
|
883
|
+
const written = fs.readFileSync(consumerLogicPath, 'utf8')
|
|
884
|
+
const orderOf = (names: string[]): void => {
|
|
885
|
+
const positions = names.map((name) => written.indexOf(name))
|
|
886
|
+
positions.forEach((position, index) => {
|
|
887
|
+
expect(position).toBeGreaterThan(-1)
|
|
888
|
+
if (index > 0) {
|
|
889
|
+
expect(position).toBeGreaterThan(positions[index - 1])
|
|
890
|
+
}
|
|
891
|
+
})
|
|
892
|
+
}
|
|
893
|
+
// values: alphaLogic group (alphabetical), betaLogic group, then own values alphabetically
|
|
894
|
+
orderOf(['alpha: string', 'bravo: string', 'charlie: string', 'anteater: number', 'zebra: number'])
|
|
895
|
+
// actions: same grouping
|
|
896
|
+
orderOf(['setAlpha:', 'setBravo:', 'setCharlie:', 'aardvark:', 'zulu:'])
|
|
897
|
+
} finally {
|
|
898
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
899
|
+
}
|
|
900
|
+
})
|
|
901
|
+
|
|
693
902
|
test('files that already carry an inline block stay inline without any inline options', async () => {
|
|
694
903
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-sticky-'))
|
|
695
904
|
|
|
@@ -738,7 +947,7 @@ describe('mixed mode with inlinePaths', () => {
|
|
|
738
947
|
|
|
739
948
|
const writtenLogic = fs.readFileSync(logicPath, 'utf8')
|
|
740
949
|
expect(writtenLogic).toContain('setOther: (other: number) =>')
|
|
741
|
-
expect(writtenLogic.match(/
|
|
950
|
+
expect(writtenLogic.match(/Update if you're an agent, ignore if you're human/g)?.length).toBe(1)
|
|
742
951
|
expect(fs.existsSync(path.join(rootDir, 'stickyLogicType.ts'))).toBe(false)
|
|
743
952
|
} finally {
|
|
744
953
|
fs.rmSync(tempDir, { recursive: true, force: true })
|
package/src/print/print.ts
CHANGED
|
@@ -323,9 +323,17 @@ export function buildTypeImportEntries(
|
|
|
323
323
|
finalPath = result[1]
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// A workspace package.json can point at a different node_modules root than the physical
|
|
329
|
+
// dependency path. Collapse the innermost package path instead of writing pnpm internals.
|
|
330
|
+
const nestedNodeModules = `${path.sep}node_modules${path.sep}`
|
|
331
|
+
const nestedNodeModulesIndex = finalPath.lastIndexOf(nestedNodeModules)
|
|
332
|
+
if (nestedNodeModulesIndex >= 0) {
|
|
333
|
+
finalPath = finalPath.substring(nestedNodeModulesIndex + nestedNodeModules.length)
|
|
334
|
+
}
|
|
335
|
+
if (finalPath.startsWith('@types/')) {
|
|
336
|
+
finalPath = finalPath.substring(7)
|
|
329
337
|
}
|
|
330
338
|
|
|
331
339
|
// Resolve absolute urls
|
package/src/utils.ts
CHANGED
|
@@ -60,11 +60,11 @@ export function hasKeaTypegenMarker(node: ts.Node): boolean {
|
|
|
60
60
|
return ranges.some(({ pos, end }) => sourceFile.text.slice(pos, end).includes('kea-typegen'))
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const INLINE_BLOCK_SUFFIXES = ['Values', 'Actions', 'Props']
|
|
63
|
+
const INLINE_BLOCK_SUFFIXES = ['Values', 'Actions', 'Props', 'Meta']
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* The statements making up an inline logic type block: the `logicType` alias itself, plus any
|
|
67
|
-
* `logicValues` / `logicActions` / `logicProps` interfaces directly above it.
|
|
67
|
+
* `logicValues` / `logicActions` / `logicProps` / `logicMeta` interfaces directly above it.
|
|
68
68
|
*/
|
|
69
69
|
export function getInlineBlockStatements(
|
|
70
70
|
declaration: ts.TypeAliasDeclaration | ts.InterfaceDeclaration,
|
|
@@ -4,11 +4,15 @@ import * as osPath from 'path'
|
|
|
4
4
|
import { factory, SyntaxKind } from 'typescript'
|
|
5
5
|
import { AppOptions, ParsedLogic } from '../types'
|
|
6
6
|
import { buildTypeImportEntries, nodeToString, runThroughPrettier } from '../print/print'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { printInternalExtraInput } from '../print/printInternalExtraInput'
|
|
8
|
+
import { printInternalReducerActions } from '../print/printInternalReducerActions'
|
|
9
|
+
import { printInternalSelectorTypes } from '../print/printInternalSelectorTypes'
|
|
10
|
+
import { printKey } from '../print/printKey'
|
|
11
|
+
import { printSharedListeners } from '../print/printSharedListeners'
|
|
12
|
+
import { cleanDuplicateAnyNodes, getInlineBlockStatements, isGeneratedInlineBlock } from '../utils'
|
|
9
13
|
import { applyTextEdits, getImportInsertPosition, getTypeArgumentInsertEnd, TextEdit } from './writeTypeImports'
|
|
10
14
|
|
|
11
|
-
export const INLINE_TYPE_MARKER =
|
|
15
|
+
export const INLINE_TYPE_MARKER = " Generated by kea-typegen. Update if you're an agent, ignore if you're human."
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* Print a block of interfaces (Values, Actions, and Props when it's a literal), plus an
|
|
@@ -40,35 +44,51 @@ export function printInlineLogicType(parsedLogic: ParsedLogic): string {
|
|
|
40
44
|
|
|
41
45
|
// mark connected values/actions with the logic they come from, e.g. `user: UserType | null // userLogic`
|
|
42
46
|
const withSourceLogicComment = <T extends ts.TypeElement>(member: T, sourceLogic: string | undefined): T =>
|
|
43
|
-
sourceLogic
|
|
47
|
+
sourceLogic
|
|
48
|
+
? ts.addSyntheticTrailingComment(member, SyntaxKind.SingleLineCommentTrivia, ` ${sourceLogic}`, false)
|
|
49
|
+
: member
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
// connected entries first, grouped by source logic (groups alphabetical, entries alphabetical
|
|
52
|
+
// within each group), then the logic's own entries alphabetically
|
|
53
|
+
const bySourceLogicThenName = (
|
|
54
|
+
a: { name: string; sourceLogic?: string },
|
|
55
|
+
b: { name: string; sourceLogic?: string },
|
|
56
|
+
): number => {
|
|
57
|
+
if (!!a.sourceLogic !== !!b.sourceLogic) {
|
|
58
|
+
return a.sourceLogic ? -1 : 1
|
|
59
|
+
}
|
|
60
|
+
if (a.sourceLogic && b.sourceLogic && a.sourceLogic !== b.sourceLogic) {
|
|
61
|
+
return a.sourceLogic < b.sourceLogic ? -1 : 1
|
|
62
|
+
}
|
|
63
|
+
return a.name === b.name ? 0 : a.name < b.name ? -1 : 1
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
addSection(
|
|
51
67
|
'Values',
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
[...cleanDuplicateAnyNodes(parsedLogic.reducers.concat(parsedLogic.selectors))]
|
|
69
|
+
.sort(bySourceLogicThenName)
|
|
70
|
+
.map(({ name, typeNode, sourceLogic }) =>
|
|
71
|
+
withSourceLogicComment(
|
|
72
|
+
factory.createPropertySignature(undefined, factory.createIdentifier(name), undefined, typeNode),
|
|
73
|
+
sourceLogic,
|
|
74
|
+
),
|
|
56
75
|
),
|
|
57
|
-
),
|
|
58
76
|
)
|
|
59
77
|
addSection(
|
|
60
78
|
'Actions',
|
|
61
|
-
parsedLogic.actions
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
factory.
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
[...parsedLogic.actions]
|
|
80
|
+
.sort(bySourceLogicThenName)
|
|
81
|
+
.map(({ name, parameters, returnTypeNode, sourceLogic }) =>
|
|
82
|
+
withSourceLogicComment(
|
|
83
|
+
factory.createPropertySignature(
|
|
84
|
+
undefined,
|
|
85
|
+
factory.createIdentifier(name),
|
|
86
|
+
undefined,
|
|
87
|
+
factory.createFunctionTypeNode(undefined, parameters, returnTypeNode),
|
|
88
|
+
),
|
|
89
|
+
sourceLogic,
|
|
68
90
|
),
|
|
69
|
-
sourceLogic,
|
|
70
91
|
),
|
|
71
|
-
),
|
|
72
92
|
)
|
|
73
93
|
if (parsedLogic.propsType) {
|
|
74
94
|
if (ts.isTypeLiteralNode(parsedLogic.propsType)) {
|
|
@@ -79,6 +99,39 @@ export function printInlineLogicType(parsedLogic: ParsedLogic): string {
|
|
|
79
99
|
}
|
|
80
100
|
}
|
|
81
101
|
|
|
102
|
+
const metaMembers: ts.TypeElement[] = []
|
|
103
|
+
const addMetaProperty = (name: string, typeNode: ts.TypeNode): void => {
|
|
104
|
+
metaMembers.push(
|
|
105
|
+
factory.createPropertySignature(undefined, factory.createIdentifier(name), undefined, typeNode),
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
if (parsedLogic.keyType) {
|
|
109
|
+
addMetaProperty('key', printKey(parsedLogic))
|
|
110
|
+
}
|
|
111
|
+
if (parsedLogic.sharedListeners.length > 0) {
|
|
112
|
+
addMetaProperty('sharedListeners', printSharedListeners(parsedLogic))
|
|
113
|
+
}
|
|
114
|
+
if (parsedLogic.selectors.some((selector) => (selector.functionTypes?.length ?? 0) > 0)) {
|
|
115
|
+
addMetaProperty('__keaTypeGenInternalSelectorTypes', printInternalSelectorTypes(parsedLogic))
|
|
116
|
+
}
|
|
117
|
+
if (Object.keys(parsedLogic.extraActions).length > 0) {
|
|
118
|
+
addMetaProperty('__keaTypeGenInternalReducerActions', printInternalReducerActions(parsedLogic))
|
|
119
|
+
}
|
|
120
|
+
if (Object.keys(parsedLogic.extraInput).length > 0) {
|
|
121
|
+
addMetaProperty('__keaTypeGenInternalExtraInput', printInternalExtraInput(parsedLogic))
|
|
122
|
+
}
|
|
123
|
+
if (metaMembers.length > 0) {
|
|
124
|
+
if (!parsedLogic.propsType) {
|
|
125
|
+
typeArguments.push(
|
|
126
|
+
factory.createTypeReferenceNode(factory.createIdentifier('Record'), [
|
|
127
|
+
factory.createKeywordTypeNode(SyntaxKind.StringKeyword),
|
|
128
|
+
factory.createKeywordTypeNode(SyntaxKind.AnyKeyword),
|
|
129
|
+
]),
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
addSection('Meta', metaMembers)
|
|
133
|
+
}
|
|
134
|
+
|
|
82
135
|
const typeAlias = factory.createTypeAliasDeclaration(
|
|
83
136
|
[factory.createModifier(SyntaxKind.ExportKeyword)],
|
|
84
137
|
factory.createIdentifier(parsedLogic.logicTypeName),
|
|
@@ -193,6 +246,12 @@ export async function writeInlineLogicTypes(
|
|
|
193
246
|
newImportLines.push(`import type { MakeLogicType } from 'kea'`)
|
|
194
247
|
}
|
|
195
248
|
}
|
|
249
|
+
if (
|
|
250
|
+
managedLogics.some((parsedLogic) => parsedLogic.sharedListeners.length > 0) &&
|
|
251
|
+
!importsNamedKeaType(importDeclarations, 'BreakPointFunction')
|
|
252
|
+
) {
|
|
253
|
+
newImportLines.push(`import type { BreakPointFunction } from 'kea'`)
|
|
254
|
+
}
|
|
196
255
|
|
|
197
256
|
// remove the now obsolete `import type { logicType } from './logicType'`, if any
|
|
198
257
|
const typeFileNameBase = parsedLogics[0].typeFileName.replace(/\.[tj]sx?$/, '')
|
|
@@ -279,13 +338,32 @@ export async function writeInlineLogicTypes(
|
|
|
279
338
|
* block - semicolons, trailing commas, quotes, line breaks - does not make us rewrite it forever.
|
|
280
339
|
*/
|
|
281
340
|
function sameGeneratedCode(existingCode: string, generatedCode: string): boolean {
|
|
282
|
-
const normalize = (code: string): string =>
|
|
283
|
-
|
|
341
|
+
const normalize = (code: string): string => {
|
|
342
|
+
const sourceFile = ts.createSourceFile(
|
|
343
|
+
'inlineLogicType.ts',
|
|
344
|
+
code,
|
|
345
|
+
ts.ScriptTarget.Latest,
|
|
346
|
+
true,
|
|
347
|
+
ts.ScriptKind.TS,
|
|
348
|
+
)
|
|
349
|
+
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
|
|
350
|
+
const visit: ts.Visitor = (node) =>
|
|
351
|
+
ts.isParenthesizedTypeNode(node)
|
|
352
|
+
? ts.visitNode(node.type, visit)
|
|
353
|
+
: ts.visitEachChild(node, visit, context)
|
|
354
|
+
return (root) => ts.visitNode(root, visit) as ts.SourceFile
|
|
355
|
+
}
|
|
356
|
+
const transformed = ts.transform(sourceFile, [transformer])
|
|
357
|
+
const normalized = ts
|
|
358
|
+
.createPrinter({ removeComments: true })
|
|
359
|
+
.printFile(transformed.transformed[0])
|
|
284
360
|
.replace(/"/g, "'")
|
|
285
|
-
.replace(/[;,]/g, '')
|
|
286
361
|
.replace(/\s+/g, '')
|
|
287
|
-
|
|
288
|
-
.replace(/([
|
|
362
|
+
.replace(/,(?=[}\])])/g, '')
|
|
363
|
+
.replace(/'([A-Za-z_$][\w$]*)':/g, '$1:')
|
|
364
|
+
transformed.dispose()
|
|
365
|
+
return normalized
|
|
366
|
+
}
|
|
289
367
|
return normalize(existingCode) === normalize(generatedCode)
|
|
290
368
|
}
|
|
291
369
|
|
|
@@ -305,6 +383,10 @@ function getTopLevelStatement(node: ts.Node): ts.Node {
|
|
|
305
383
|
}
|
|
306
384
|
|
|
307
385
|
function importsMakeLogicType(importDeclarations: ts.ImportDeclaration[]): boolean {
|
|
386
|
+
return importsNamedKeaType(importDeclarations, 'MakeLogicType')
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function importsNamedKeaType(importDeclarations: ts.ImportDeclaration[], name: string): boolean {
|
|
308
390
|
return importDeclarations.some(
|
|
309
391
|
(declaration) =>
|
|
310
392
|
ts.isStringLiteralLike(declaration.moduleSpecifier) &&
|
|
@@ -312,7 +394,7 @@ function importsMakeLogicType(importDeclarations: ts.ImportDeclaration[]): boole
|
|
|
312
394
|
declaration.importClause?.namedBindings &&
|
|
313
395
|
ts.isNamedImports(declaration.importClause.namedBindings) &&
|
|
314
396
|
declaration.importClause.namedBindings.elements.some(
|
|
315
|
-
(element) => element.name.text ===
|
|
397
|
+
(element) => element.name.text === name && !element.propertyName,
|
|
316
398
|
),
|
|
317
399
|
)
|
|
318
400
|
}
|