kea-typegen 3.8.0 → 3.8.1

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.
Files changed (38) hide show
  1. package/README.md +33 -0
  2. package/dist/package.json +1 -1
  3. package/dist/src/__tests__/inline.js +383 -1
  4. package/dist/src/__tests__/inline.js.map +1 -1
  5. package/dist/src/cache.d.ts +1 -0
  6. package/dist/src/cache.js +19 -0
  7. package/dist/src/cache.js.map +1 -1
  8. package/dist/src/cli/typegen.js +5 -0
  9. package/dist/src/cli/typegen.js.map +1 -1
  10. package/dist/src/print/print.js +6 -4
  11. package/dist/src/print/print.js.map +1 -1
  12. package/dist/src/types.d.ts +8 -0
  13. package/dist/src/utils.d.ts +2 -0
  14. package/dist/src/utils.js +27 -1
  15. package/dist/src/utils.js.map +1 -1
  16. package/dist/src/visit/visit.js +1 -0
  17. package/dist/src/visit/visit.js.map +1 -1
  18. package/dist/src/visit/visitConnect.js +40 -0
  19. package/dist/src/visit/visitConnect.js.map +1 -1
  20. package/dist/src/visit/visitSelectors.js +14 -1
  21. package/dist/src/visit/visitSelectors.js.map +1 -1
  22. package/dist/src/write/writeInlineLogicTypes.js +32 -9
  23. package/dist/src/write/writeInlineLogicTypes.js.map +1 -1
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/package.json +1 -1
  26. package/samples-inline/counterLogic.ts +2 -1
  27. package/samples-inline/profileLogic.ts +1 -0
  28. package/samples-inline/searchLogic.ts +2 -0
  29. package/src/__tests__/inline.ts +482 -1
  30. package/src/cache.ts +19 -0
  31. package/src/cli/typegen.ts +6 -0
  32. package/src/print/print.ts +9 -7
  33. package/src/types.ts +13 -0
  34. package/src/utils.ts +43 -1
  35. package/src/visit/visit.ts +1 -0
  36. package/src/visit/visitConnect.ts +50 -0
  37. package/src/visit/visitSelectors.ts +26 -1
  38. package/src/write/writeInlineLogicTypes.ts +67 -18
@@ -194,7 +194,8 @@ describe('inline mode', () => {
194
194
  expect(updatedLogic.match(/export type logicType = MakeLogicType</g)?.length).toBe(1)
195
195
  expect(updatedLogic.match(/export interface logicValues \{/g)?.length).toBe(1)
196
196
  expect(updatedLogic.match(/export interface logicActions \{/g)?.length).toBe(1)
197
- expect(updatedLogic.match(/DO NOT EDIT THIS BLOCK MANUALLY/g)?.length).toBe(1)
197
+ // one marker per generated interface (values, actions, props)
198
+ expect(updatedLogic.match(/DO NOT EDIT THIS BLOCK MANUALLY/g)?.length).toBe(3)
198
199
  } finally {
199
200
  fs.rmSync(tempDir, { recursive: true, force: true })
200
201
  }
@@ -263,4 +264,484 @@ describe('inline mode', () => {
263
264
  fs.rmSync(tempDir, { recursive: true, force: true })
264
265
  }
265
266
  })
267
+
268
+ test('leaves a reformatted but semantically identical block alone', async () => {
269
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-reformat-'))
270
+
271
+ try {
272
+ const logicDir = path.join(tempDir, 'src')
273
+ const logicPath = path.join(logicDir, 'logic.ts')
274
+ const keaDtsPath = path.join(tempDir, 'node_modules', 'kea', 'index.d.ts')
275
+
276
+ fs.mkdirSync(path.dirname(keaDtsPath), { recursive: true })
277
+ fs.mkdirSync(logicDir, { recursive: true })
278
+
279
+ fs.writeFileSync(
280
+ keaDtsPath,
281
+ [
282
+ 'export function kea<T = any>(input: any): T',
283
+ 'export interface MakeLogicType<V = any, A = any, P = any> {}',
284
+ '',
285
+ ].join('\n'),
286
+ )
287
+ fs.writeFileSync(
288
+ logicPath,
289
+ [
290
+ "import { MakeLogicType, kea } from 'kea'",
291
+ '',
292
+ 'export const logic = kea({',
293
+ ' actions: () => ({',
294
+ ' setValue: (value: string | number) => ({ value }),',
295
+ ' }),',
296
+ '})',
297
+ '',
298
+ ].join('\n'),
299
+ )
300
+
301
+ const appOptions: AppOptions = {
302
+ rootPath: logicDir,
303
+ typesPath: logicDir,
304
+ packageJsonPath: path.join(tempDir, 'package.json'),
305
+ inline: true,
306
+ write: true,
307
+ log: () => {},
308
+ }
309
+
310
+ const program = createProgram([logicPath])
311
+ await printToFiles(program, appOptions, visitProgram(program, appOptions))
312
+
313
+ // simulate a formatter pass: strip semicolons and reflow unions with a leading pipe
314
+ const formatted = fs
315
+ .readFileSync(logicPath, 'utf8')
316
+ .replace(/;$/gm, '')
317
+ .replace(/value: string \| number\) =>/g, 'value:\n | string\n | number\n ) =>')
318
+ fs.writeFileSync(logicPath, formatted)
319
+
320
+ const secondProgram = createProgram([logicPath])
321
+ const secondResponse = await printToFiles(secondProgram, appOptions, visitProgram(secondProgram, appOptions))
322
+ expect(secondResponse).toEqual({ filesToWrite: 0, writtenFiles: 0, filesToModify: 0 })
323
+ expect(fs.readFileSync(logicPath, 'utf8')).toBe(formatted)
324
+ } finally {
325
+ fs.rmSync(tempDir, { recursive: true, force: true })
326
+ }
327
+ })
328
+
329
+ test('keeps leading comments attached to the logic when inserting the block', async () => {
330
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-comments-'))
331
+
332
+ try {
333
+ const logicDir = path.join(tempDir, 'src')
334
+ const logicPath = path.join(logicDir, 'logic.ts')
335
+ const keaDtsPath = path.join(tempDir, 'node_modules', 'kea', 'index.d.ts')
336
+
337
+ fs.mkdirSync(path.dirname(keaDtsPath), { recursive: true })
338
+ fs.mkdirSync(logicDir, { recursive: true })
339
+
340
+ fs.writeFileSync(
341
+ keaDtsPath,
342
+ [
343
+ 'export function kea<T = any>(input: any): T',
344
+ 'export interface MakeLogicType<V = any, A = any, P = any> {}',
345
+ '',
346
+ ].join('\n'),
347
+ )
348
+ fs.writeFileSync(
349
+ logicPath,
350
+ [
351
+ "import { kea } from 'kea'",
352
+ '',
353
+ '// this comment describes the logic and must stay attached to it',
354
+ 'export const logic = kea({',
355
+ ' actions: () => ({',
356
+ ' setValue: (value: string) => ({ value }),',
357
+ ' }),',
358
+ '})',
359
+ '',
360
+ ].join('\n'),
361
+ )
362
+
363
+ const appOptions: AppOptions = {
364
+ rootPath: logicDir,
365
+ typesPath: logicDir,
366
+ packageJsonPath: path.join(tempDir, 'package.json'),
367
+ inline: true,
368
+ write: true,
369
+ log: () => {},
370
+ }
371
+
372
+ const program = createProgram([logicPath])
373
+ const response = await printToFiles(program, appOptions, visitProgram(program, appOptions))
374
+ expect(response.writtenFiles).toBe(1)
375
+
376
+ const writtenLogic = fs.readFileSync(logicPath, 'utf8')
377
+ expect(writtenLogic).toContain(
378
+ [
379
+ '// this comment describes the logic and must stay attached to it',
380
+ 'export const logic = kea<logicType>({',
381
+ ].join('\n'),
382
+ )
383
+ // the generated block sits above the comment
384
+ expect(writtenLogic.indexOf('DO NOT EDIT THIS BLOCK MANUALLY')).toBeLessThan(
385
+ writtenLogic.indexOf('// this comment describes the logic'),
386
+ )
387
+ } finally {
388
+ fs.rmSync(tempDir, { recursive: true, force: true })
389
+ }
390
+ })
391
+
392
+ test('annotates untyped selector combiner parameters', async () => {
393
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-selectors-'))
394
+
395
+ try {
396
+ const logicDir = path.join(tempDir, 'src')
397
+ const logicPath = path.join(logicDir, 'logic.ts')
398
+ const keaDtsPath = path.join(tempDir, 'node_modules', 'kea', 'index.d.ts')
399
+
400
+ fs.mkdirSync(path.dirname(keaDtsPath), { recursive: true })
401
+ fs.mkdirSync(logicDir, { recursive: true })
402
+
403
+ fs.writeFileSync(
404
+ keaDtsPath,
405
+ [
406
+ 'export function kea<T = any>(input: any): T',
407
+ 'export interface MakeLogicType<V = any, A = any, P = any> {}',
408
+ '',
409
+ ].join('\n'),
410
+ )
411
+ fs.writeFileSync(
412
+ logicPath,
413
+ [
414
+ "import { kea } from 'kea'",
415
+ '',
416
+ 'export const logic = kea({',
417
+ ' actions: () => ({',
418
+ ' setCounter: (counter: number) => ({ counter }),',
419
+ ' }),',
420
+ ' reducers: () => ({',
421
+ ' counter: [0 as number, { setCounter: (_, { counter }) => counter }],',
422
+ ' }),',
423
+ ' selectors: () => ({',
424
+ ' doubleCounter: [(s): [() => number] => [s.counter], (counter) => counter * 2],',
425
+ ' typedCounter: [(s): [() => number] => [s.counter], (counter: number) => counter * 3],',
426
+ ' }),',
427
+ '})',
428
+ '',
429
+ ].join('\n'),
430
+ )
431
+
432
+ const appOptions: AppOptions = {
433
+ rootPath: logicDir,
434
+ typesPath: logicDir,
435
+ packageJsonPath: path.join(tempDir, 'package.json'),
436
+ inline: true,
437
+ write: true,
438
+ log: () => {},
439
+ }
440
+
441
+ const program = createProgram([logicPath])
442
+ const response = await printToFiles(program, appOptions, visitProgram(program, appOptions))
443
+ expect(response.writtenFiles).toBe(1)
444
+
445
+ const writtenLogic = fs.readFileSync(logicPath, 'utf8')
446
+ expect(writtenLogic).toContain('(counter: number) => counter * 2')
447
+ // already annotated params stay untouched
448
+ expect(writtenLogic).toContain('(counter: number) => counter * 3')
449
+
450
+ // a second pass changes nothing
451
+ const secondProgram = createProgram([logicPath])
452
+ const secondResponse = await printToFiles(secondProgram, appOptions, visitProgram(secondProgram, appOptions))
453
+ expect(secondResponse).toEqual({ filesToWrite: 0, writtenFiles: 0, filesToModify: 0 })
454
+ } finally {
455
+ fs.rmSync(tempDir, { recursive: true, force: true })
456
+ }
457
+ })
458
+ })
459
+
460
+ describe('mixed mode with inlinePaths', () => {
461
+ const createProgram = (fileNames: string[]) =>
462
+ ts.createProgram(fileNames, {
463
+ module: ts.ModuleKind.CommonJS,
464
+ moduleResolution: ts.ModuleResolutionKind.NodeJs,
465
+ target: ts.ScriptTarget.ES2020,
466
+ skipLibCheck: true,
467
+ })
468
+
469
+ const writeKeaDts = (tempDir: string) => {
470
+ const keaDtsPath = path.join(tempDir, 'node_modules', 'kea', 'index.d.ts')
471
+ fs.mkdirSync(path.dirname(keaDtsPath), { recursive: true })
472
+ fs.writeFileSync(
473
+ keaDtsPath,
474
+ [
475
+ 'export function kea<T = any>(input: any): T',
476
+ 'export interface MakeLogicType<V = any, A = any, P = any> {}',
477
+ '',
478
+ ].join('\n'),
479
+ )
480
+ }
481
+
482
+ const simpleLogicSource = [
483
+ "import { kea } from 'kea'",
484
+ '',
485
+ 'export const logic = kea({',
486
+ ' actions: () => ({',
487
+ ' setValue: (value: string) => ({ value }),',
488
+ ' }),',
489
+ '})',
490
+ '',
491
+ ].join('\n')
492
+
493
+ test('files under inlinePaths go inline, everything else keeps logicType.ts', async () => {
494
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-paths-'))
495
+
496
+ try {
497
+ const rootDir = path.join(tempDir, 'src')
498
+ const inlineDir = path.join(rootDir, 'scenes', 'inlined')
499
+ const classicDir = path.join(rootDir, 'scenes', 'classic')
500
+ const inlineLogicPath = path.join(inlineDir, 'inlinedLogic.ts')
501
+ const classicLogicPath = path.join(classicDir, 'classicLogic.ts')
502
+
503
+ writeKeaDts(tempDir)
504
+ fs.mkdirSync(inlineDir, { recursive: true })
505
+ fs.mkdirSync(classicDir, { recursive: true })
506
+ fs.writeFileSync(inlineLogicPath, simpleLogicSource.replace('export const logic', 'export const inlinedLogic'))
507
+ fs.writeFileSync(classicLogicPath, simpleLogicSource.replace('export const logic', 'export const classicLogic'))
508
+
509
+ const appOptions: AppOptions = {
510
+ rootPath: rootDir,
511
+ typesPath: rootDir,
512
+ packageJsonPath: path.join(tempDir, 'package.json'),
513
+ inlinePaths: [path.join(rootDir, 'scenes', 'inlined')],
514
+ write: true,
515
+ delete: true,
516
+ log: () => {},
517
+ }
518
+
519
+ const program = createProgram([inlineLogicPath, classicLogicPath])
520
+ await printToFiles(program, appOptions, visitProgram(program, appOptions))
521
+
522
+ const writtenInlineLogic = fs.readFileSync(inlineLogicPath, 'utf8')
523
+ expect(writtenInlineLogic).toContain('// Generated by kea-typegen. DO NOT EDIT THIS BLOCK MANUALLY.')
524
+ expect(writtenInlineLogic).toContain('export type inlinedLogicType = MakeLogicType<{}, inlinedLogicActions>')
525
+ expect(fs.existsSync(path.join(inlineDir, 'inlinedLogicType.ts'))).toBe(false)
526
+
527
+ const writtenClassicLogic = fs.readFileSync(classicLogicPath, 'utf8')
528
+ expect(writtenClassicLogic).not.toContain('MakeLogicType')
529
+ expect(fs.existsSync(path.join(classicDir, 'classicLogicType.ts'))).toBe(true)
530
+ } finally {
531
+ fs.rmSync(tempDir, { recursive: true, force: true })
532
+ }
533
+ })
534
+
535
+ test('relative inlinePaths resolve against rootPath', async () => {
536
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-paths-relative-'))
537
+
538
+ try {
539
+ const rootDir = path.join(tempDir, 'src')
540
+ const inlineDir = path.join(rootDir, 'scenes', 'inlined')
541
+ const inlineLogicPath = path.join(inlineDir, 'inlinedLogic.ts')
542
+
543
+ writeKeaDts(tempDir)
544
+ fs.mkdirSync(inlineDir, { recursive: true })
545
+ fs.writeFileSync(inlineLogicPath, simpleLogicSource.replace('export const logic', 'export const inlinedLogic'))
546
+
547
+ const appOptions: AppOptions = {
548
+ rootPath: rootDir,
549
+ typesPath: rootDir,
550
+ packageJsonPath: path.join(tempDir, 'package.json'),
551
+ inlinePaths: ['./scenes/inlined'],
552
+ write: true,
553
+ log: () => {},
554
+ }
555
+
556
+ const program = createProgram([inlineLogicPath])
557
+ await printToFiles(program, appOptions, visitProgram(program, appOptions))
558
+
559
+ expect(fs.readFileSync(inlineLogicPath, 'utf8')).toContain(
560
+ '// Generated by kea-typegen. DO NOT EDIT THIS BLOCK MANUALLY.',
561
+ )
562
+ expect(fs.existsSync(path.join(inlineDir, 'inlinedLogicType.ts'))).toBe(false)
563
+ } finally {
564
+ fs.rmSync(tempDir, { recursive: true, force: true })
565
+ }
566
+ })
567
+
568
+ test('connected actions from an inline (MakeLogicType) logic land in the consuming logic type', async () => {
569
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-connect-'))
570
+
571
+ try {
572
+ const rootDir = path.join(tempDir, 'src')
573
+ const inlineLogicPath = path.join(rootDir, 'inlinedLogic.ts')
574
+ const consumerLogicPath = path.join(rootDir, 'consumerLogic.ts')
575
+ const keaDtsPath = path.join(tempDir, 'node_modules', 'kea', 'index.d.ts')
576
+
577
+ fs.mkdirSync(path.dirname(keaDtsPath), { recursive: true })
578
+ fs.mkdirSync(rootDir, { recursive: true })
579
+
580
+ // minimal kea with a mapped-type MakeLogicType, mirroring the real shape
581
+ fs.writeFileSync(
582
+ keaDtsPath,
583
+ [
584
+ 'export function kea<T = any>(input: any): T',
585
+ 'export type AnyFunction = (...args: any) => any',
586
+ 'export interface MakeLogicType<V = any, A = any, P = any> {',
587
+ ' actionCreators: {',
588
+ ' [K in keyof A]: A[K] extends AnyFunction',
589
+ ' ? (...args: Parameters<A[K]>) => { type: string; payload: ReturnType<A[K]> }',
590
+ ' : never',
591
+ ' }',
592
+ ' values: V',
593
+ '}',
594
+ '',
595
+ ].join('\n'),
596
+ )
597
+ fs.writeFileSync(
598
+ inlineLogicPath,
599
+ [
600
+ "import { MakeLogicType, kea } from 'kea'",
601
+ '',
602
+ '// Generated by kea-typegen. DO NOT EDIT THIS BLOCK MANUALLY.',
603
+ 'export interface inlinedLogicValues {',
604
+ ' value: string',
605
+ '}',
606
+ '',
607
+ 'export interface inlinedLogicActions {',
608
+ ' setValue: (value: string) => {',
609
+ ' value: string',
610
+ ' }',
611
+ '}',
612
+ '',
613
+ 'export type inlinedLogicType = MakeLogicType<inlinedLogicValues, inlinedLogicActions>',
614
+ '',
615
+ 'export const inlinedLogic = kea<inlinedLogicType>({',
616
+ ' actions: () => ({',
617
+ ' setValue: (value: string) => ({ value }),',
618
+ ' }),',
619
+ ' reducers: () => ({',
620
+ " value: ['' as string, { setValue: (_, { value }) => value }],",
621
+ ' }),',
622
+ '})',
623
+ '',
624
+ ].join('\n'),
625
+ )
626
+ fs.writeFileSync(
627
+ consumerLogicPath,
628
+ [
629
+ "import { kea } from 'kea'",
630
+ '',
631
+ "import { inlinedLogic } from './inlinedLogic'",
632
+ '',
633
+ 'export const consumerLogic = kea({',
634
+ ' connect: {',
635
+ " actions: [inlinedLogic, ['setValue']],",
636
+ " values: [inlinedLogic, ['value']],",
637
+ ' },',
638
+ '})',
639
+ '',
640
+ ].join('\n'),
641
+ )
642
+ const inlineConsumerDir = path.join(rootDir, 'inlineConsumer')
643
+ const inlineConsumerLogicPath = path.join(inlineConsumerDir, 'inlineConsumerLogic.ts')
644
+ fs.mkdirSync(inlineConsumerDir, { recursive: true })
645
+ fs.writeFileSync(
646
+ inlineConsumerLogicPath,
647
+ [
648
+ "import { kea } from 'kea'",
649
+ '',
650
+ "import { inlinedLogic } from '../inlinedLogic'",
651
+ '',
652
+ 'export const inlineConsumerLogic = kea({',
653
+ ' connect: {',
654
+ " actions: [inlinedLogic, ['setValue']],",
655
+ " values: [inlinedLogic, ['value']],",
656
+ ' },',
657
+ '})',
658
+ '',
659
+ ].join('\n'),
660
+ )
661
+
662
+ const appOptions: AppOptions = {
663
+ rootPath: rootDir,
664
+ typesPath: rootDir,
665
+ packageJsonPath: path.join(tempDir, 'package.json'),
666
+ inlinePaths: [inlineConsumerDir],
667
+ write: true,
668
+ log: () => {},
669
+ }
670
+
671
+ const program = createProgram([inlineLogicPath, consumerLogicPath, inlineConsumerLogicPath])
672
+ await printToFiles(program, appOptions, visitProgram(program, appOptions))
673
+
674
+ // consumerLogic is classic-mode, so it gets a logicType.ts that must include the connected action
675
+ const consumerType = fs.readFileSync(path.join(rootDir, 'consumerLogicType.ts'), 'utf8')
676
+ expect(consumerType).toContain('setValue: (value: string) => void')
677
+ expect(consumerType).toContain('value: string')
678
+
679
+ // the inline consumer's block marks connected values/actions with their source logic
680
+ const inlineConsumer = fs.readFileSync(inlineConsumerLogicPath, 'utf8')
681
+ expect(inlineConsumer).toMatch(/value: string.* \/\/ inlinedLogic/)
682
+ expect(inlineConsumer).toMatch(/\};? \/\/ inlinedLogic/)
683
+
684
+ // a second pass over the annotated block changes nothing
685
+ const secondProgram = createProgram([inlineLogicPath, consumerLogicPath, inlineConsumerLogicPath])
686
+ const secondResponse = await printToFiles(secondProgram, appOptions, visitProgram(secondProgram, appOptions))
687
+ expect(secondResponse).toEqual({ filesToWrite: 0, writtenFiles: 0, filesToModify: 0 })
688
+ } finally {
689
+ fs.rmSync(tempDir, { recursive: true, force: true })
690
+ }
691
+ })
692
+
693
+ test('files that already carry an inline block stay inline without any inline options', async () => {
694
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-sticky-'))
695
+
696
+ try {
697
+ const rootDir = path.join(tempDir, 'src')
698
+ const logicPath = path.join(rootDir, 'stickyLogic.ts')
699
+
700
+ writeKeaDts(tempDir)
701
+ fs.mkdirSync(rootDir, { recursive: true })
702
+ fs.writeFileSync(
703
+ logicPath,
704
+ [
705
+ "import { MakeLogicType, kea } from 'kea'",
706
+ '',
707
+ '// Generated by kea-typegen. DO NOT EDIT THIS BLOCK MANUALLY.',
708
+ 'export interface stickyLogicActions {',
709
+ ' setValue: (value: string) => {',
710
+ ' value: string',
711
+ ' }',
712
+ '}',
713
+ '',
714
+ 'export type stickyLogicType = MakeLogicType<{}, stickyLogicActions>',
715
+ '',
716
+ 'export const stickyLogic = kea<stickyLogicType>({',
717
+ ' actions: () => ({',
718
+ ' setValue: (value: string) => ({ value }),',
719
+ ' setOther: (other: number) => ({ other }),',
720
+ ' }),',
721
+ '})',
722
+ '',
723
+ ].join('\n'),
724
+ )
725
+
726
+ // no `inline`, no `inlinePaths` - the existing block alone keeps the file inline
727
+ const appOptions: AppOptions = {
728
+ rootPath: rootDir,
729
+ typesPath: rootDir,
730
+ packageJsonPath: path.join(tempDir, 'package.json'),
731
+ write: true,
732
+ log: () => {},
733
+ }
734
+
735
+ const program = createProgram([logicPath])
736
+ const response = await printToFiles(program, appOptions, visitProgram(program, appOptions))
737
+ expect(response.writtenFiles).toBe(1)
738
+
739
+ const writtenLogic = fs.readFileSync(logicPath, 'utf8')
740
+ expect(writtenLogic).toContain('setOther: (other: number) =>')
741
+ expect(writtenLogic.match(/DO NOT EDIT THIS BLOCK MANUALLY/g)?.length).toBe(1)
742
+ expect(fs.existsSync(path.join(rootDir, 'stickyLogicType.ts'))).toBe(false)
743
+ } finally {
744
+ fs.rmSync(tempDir, { recursive: true, force: true })
745
+ }
746
+ })
266
747
  })
package/src/cache.ts CHANGED
@@ -3,6 +3,7 @@ import * as path from 'path'
3
3
  import { AppOptions, ParsedLogic } from './types'
4
4
  import { Program } from 'typescript'
5
5
  import { visitProgram } from './visit/visit'
6
+ import { isInlineFile } from './utils'
6
7
 
7
8
  export function cachePath(appOptions: AppOptions, fileName: string): string {
8
9
  return path.join(process.cwd(), '.typegen', path.relative(process.cwd(), fileName))
@@ -13,8 +14,16 @@ export function restoreCachedTypes(program: Program, appOptions: AppOptions, log
13
14
  return false
14
15
  }
15
16
  const parsedLogics = visitProgram(program, appOptions)
17
+ const parsedLogicsByFile: Record<string, ParsedLogic[]> = {}
18
+ for (const pl of parsedLogics) {
19
+ parsedLogicsByFile[pl.fileName] = [...(parsedLogicsByFile[pl.fileName] ?? []), pl]
20
+ }
16
21
  let restored = false
17
22
  for (const pl of parsedLogics) {
23
+ if (isInlineFile(appOptions, program.getSourceFile(pl.fileName), parsedLogicsByFile[pl.fileName])) {
24
+ // inline files have their types in the logic file itself, there is no logicType.ts to restore
25
+ continue
26
+ }
18
27
  if (!fs.existsSync(pl.typeFileName)) {
19
28
  const from = cachePath(appOptions, pl.typeFileName)
20
29
  if (fs.existsSync(from)) {
@@ -36,3 +45,13 @@ export function cacheWrittenFile(fileName: string, appOptions: AppOptions) {
36
45
  fs.mkdirSync(path.dirname(dest), { recursive: true })
37
46
  fs.copyFileSync(fileName, dest)
38
47
  }
48
+
49
+ export function deleteCachedFile(fileName: string, appOptions: AppOptions) {
50
+ if (!appOptions.useCache) {
51
+ return
52
+ }
53
+ const dest = cachePath(appOptions, fileName)
54
+ if (fs.existsSync(dest)) {
55
+ fs.unlinkSync(dest)
56
+ }
57
+ }
@@ -43,6 +43,12 @@ yargs
43
43
  describe: 'Write logic types as MakeLogicType blocks above each kea() call, instead of logicType.ts files',
44
44
  type: 'boolean',
45
45
  })
46
+ .option('inline-paths', {
47
+ describe:
48
+ 'Like --inline, but only for logic files under these paths (relative to --root). ' +
49
+ 'Everything else keeps its logicType.ts file.',
50
+ type: 'array',
51
+ })
46
52
  .option('import-global-types', {
47
53
  describe: 'Add import statements in logicType.ts files for global types (e.g. @types/node)',
48
54
  type: 'boolean',
@@ -39,7 +39,8 @@ import { writeTypeImports } from '../write/writeTypeImports'
39
39
  import { writeInlineLogicTypes } from '../write/writeInlineLogicTypes'
40
40
  import { printInternalExtraInput } from './printInternalExtraInput'
41
41
  import { convertToBuilders } from '../write/convertToBuilders'
42
- import { cacheWrittenFile } from '../cache'
42
+ import { cacheWrittenFile, deleteCachedFile } from '../cache'
43
+ import { isInlineFile } from '../utils'
43
44
 
44
45
  const prettierConfigCache = new Map<string, Promise<prettier.Options | null>>()
45
46
 
@@ -78,11 +79,6 @@ export async function printToFiles(
78
79
  groupedByFile[parsedLogic.fileName] = []
79
80
  }
80
81
  groupedByFile[parsedLogic.fileName].push(parsedLogic)
81
-
82
- if (!appOptions.inline) {
83
- // create the Nodes and gather referenced types
84
- printLogicType(parsedLogic, appOptions)
85
- }
86
82
  }
87
83
 
88
84
  // Automatically ignore imports from "node_modules/@types/node", if {types: ["node"]} in tsconfig.json
@@ -120,7 +116,7 @@ export async function printToFiles(
120
116
  for (const [fileName, parsedLogics] of Object.entries(groupedByFile)) {
121
117
  const typeFileName = parsedLogics[0].typeFileName
122
118
 
123
- if (appOptions.inline) {
119
+ if (isInlineFile(appOptions, program.getSourceFile(fileName), parsedLogics)) {
124
120
  // write/update MakeLogicType blocks above the logics instead of writing a logicType.ts file
125
121
  const inlineResponse = await writeInlineLogicTypes(
126
122
  program,
@@ -137,10 +133,16 @@ export async function printToFiles(
137
133
  if (appOptions.delete && fs.existsSync(typeFileName)) {
138
134
  log(`🗑️ Deleting: ${path.relative(process.cwd(), typeFileName)}`)
139
135
  fs.unlinkSync(typeFileName)
136
+ deleteCachedFile(typeFileName, appOptions)
140
137
  }
141
138
  continue
142
139
  }
143
140
 
141
+ // create the Nodes and gather referenced types
142
+ for (const parsedLogic of parsedLogics) {
143
+ printLogicType(parsedLogic, appOptions)
144
+ }
145
+
144
146
  const logicStrings = []
145
147
  const requiredKeys = new Set(['Logic'])
146
148
  for (const parsedLogic of parsedLogics) {
package/src/types.ts CHANGED
@@ -5,11 +5,15 @@ export interface ActionTransform {
5
5
  name: string
6
6
  parameters: ts.ParameterDeclaration[]
7
7
  returnTypeNode: ts.TypeNode
8
+ /** Name of the logic this action was connected from, e.g. "userLogic" */
9
+ sourceLogic?: string
8
10
  }
9
11
 
10
12
  export interface NameType {
11
13
  name: string
12
14
  typeNode: ts.TypeNode | ts.KeywordTypeNode | ts.ParenthesizedTypeNode
15
+ /** Name of the logic this value was connected from, e.g. "userLogic" */
16
+ sourceLogic?: string
13
17
  }
14
18
 
15
19
  export interface ReducerTransform extends NameType {}
@@ -18,6 +22,12 @@ export interface SelectorTransform extends NameType {
18
22
  functionTypes?: { name: string; type: ts.TypeNode }[]
19
23
  }
20
24
 
25
+ /** A selector combiner parameter without a type annotation, plus the inferred type to write into the source */
26
+ export interface SelectorParamAnnotation {
27
+ parameter: ts.ParameterDeclaration
28
+ typeNode: ts.TypeNode
29
+ }
30
+
21
31
  export interface ListenerTransform {
22
32
  name: string
23
33
  action: ts.TypeNode | ts.KeywordTypeNode | ts.ParenthesizedTypeNode
@@ -46,6 +56,7 @@ export interface ParsedLogic {
46
56
  propsType?: ts.TypeNode
47
57
  keyType?: ts.TypeNode
48
58
  typeReferencesToImportFromFiles: Record<string, Set<string>>
59
+ selectorParamAnnotations: SelectorParamAnnotation[]
49
60
  interfaceDeclaration?: ts.InterfaceDeclaration
50
61
  extraActions: Record<string, ts.TypeNode>
51
62
  extraInput: Record<string, { typeNode: ts.TypeNode; withLogicFunction: boolean }>
@@ -80,6 +91,8 @@ export interface AppOptions {
80
91
  convertToBuilders?: boolean
81
92
  /** Write logic types as MakeLogicType blocks above each kea() call, instead of into logicType.ts files */
82
93
  inline?: boolean
94
+ /** Like `inline`, but only for logic files under these paths. Everything else keeps its logicType.ts file. */
95
+ inlinePaths?: string[]
83
96
  /** Show TypeScript errors */
84
97
  showTsErrors?: boolean
85
98
  /** Cache generated logic files into .typegen, use them if generating a logic type for the first time */