kea-typegen 3.8.0 → 3.8.2
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 +34 -0
- package/dist/package.json +1 -1
- package/dist/src/__tests__/inline.js +489 -2
- package/dist/src/__tests__/inline.js.map +1 -1
- package/dist/src/cache.d.ts +1 -0
- package/dist/src/cache.js +19 -0
- package/dist/src/cache.js.map +1 -1
- package/dist/src/cli/typegen.js +5 -0
- package/dist/src/cli/typegen.js.map +1 -1
- package/dist/src/print/print.js +6 -4
- package/dist/src/print/print.js.map +1 -1
- package/dist/src/types.d.ts +8 -0
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.js +27 -1
- package/dist/src/utils.js.map +1 -1
- package/dist/src/visit/visit.js +1 -0
- package/dist/src/visit/visit.js.map +1 -1
- package/dist/src/visit/visitConnect.js +40 -0
- package/dist/src/visit/visitConnect.js.map +1 -1
- package/dist/src/visit/visitSelectors.js +14 -1
- package/dist/src/visit/visitSelectors.js.map +1 -1
- package/dist/src/write/writeInlineLogicTypes.d.ts +1 -1
- package/dist/src/write/writeInlineLogicTypes.js +42 -11
- 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 +3 -2
- package/samples-inline/profileLogic.ts +2 -1
- package/samples-inline/searchLogic.ts +3 -1
- package/src/__tests__/inline.ts +607 -2
- package/src/cache.ts +19 -0
- package/src/cli/typegen.ts +6 -0
- package/src/print/print.ts +9 -7
- package/src/types.ts +13 -0
- package/src/utils.ts +43 -1
- package/src/visit/visit.ts +1 -0
- package/src/visit/visitConnect.ts +50 -0
- package/src/visit/visitSelectors.ts +26 -1
- package/src/write/writeInlineLogicTypes.ts +84 -22
package/src/__tests__/inline.ts
CHANGED
|
@@ -158,7 +158,7 @@ describe('inline mode', () => {
|
|
|
158
158
|
const writtenLogic = fs.readFileSync(logicPath, 'utf8')
|
|
159
159
|
|
|
160
160
|
expect(writtenLogic).toContain("import { MakeLogicType, kea } from 'kea'")
|
|
161
|
-
expect(writtenLogic).toContain(
|
|
161
|
+
expect(writtenLogic).toContain("// Generated by kea-typegen. Update if you're an agent, ignore if you're human.")
|
|
162
162
|
expect(writtenLogic).toContain('export interface logicValues {')
|
|
163
163
|
expect(writtenLogic).toContain('export interface logicActions {')
|
|
164
164
|
expect(writtenLogic).toContain('export interface logicProps {')
|
|
@@ -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
|
-
|
|
197
|
+
// one marker per generated interface (values, actions, props)
|
|
198
|
+
expect(updatedLogic.match(/Update if you're an agent, ignore if you're human/g)?.length).toBe(3)
|
|
198
199
|
} finally {
|
|
199
200
|
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
200
201
|
}
|
|
@@ -263,4 +264,608 @@ 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("Update if you're an agent, ignore if you're human")).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. Update if you're an agent, ignore if you're human.")
|
|
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. Update if you're an agent, ignore if you're human.",
|
|
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('sorts values and actions: connected first grouped by source logic, then own, all alphabetical', async () => {
|
|
694
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-sort-'))
|
|
695
|
+
|
|
696
|
+
try {
|
|
697
|
+
const rootDir = path.join(tempDir, 'src')
|
|
698
|
+
const keaDtsPath = path.join(tempDir, 'node_modules', 'kea', 'index.d.ts')
|
|
699
|
+
fs.mkdirSync(path.dirname(keaDtsPath), { recursive: true })
|
|
700
|
+
fs.mkdirSync(rootDir, { recursive: true })
|
|
701
|
+
fs.writeFileSync(
|
|
702
|
+
keaDtsPath,
|
|
703
|
+
[
|
|
704
|
+
'export function kea<T = any>(input: any): T',
|
|
705
|
+
'export type AnyFunction = (...args: any) => any',
|
|
706
|
+
'export interface MakeLogicType<V = any, A = any, P = any> {',
|
|
707
|
+
' actionCreators: {',
|
|
708
|
+
' [K in keyof A]: A[K] extends AnyFunction',
|
|
709
|
+
' ? (...args: Parameters<A[K]>) => { type: string; payload: ReturnType<A[K]> }',
|
|
710
|
+
' : never',
|
|
711
|
+
' }',
|
|
712
|
+
' values: V',
|
|
713
|
+
'}',
|
|
714
|
+
'',
|
|
715
|
+
].join('\n'),
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
const makeProvider = (name: string, entries: [string, string][]): string =>
|
|
719
|
+
[
|
|
720
|
+
"import { MakeLogicType, kea } from 'kea'",
|
|
721
|
+
'',
|
|
722
|
+
'// Generated by kea-typegen. DO NOT EDIT THIS BLOCK MANUALLY.',
|
|
723
|
+
`export interface ${name}Values {`,
|
|
724
|
+
...entries.map(([value]) => ` ${value}: string`),
|
|
725
|
+
'}',
|
|
726
|
+
'',
|
|
727
|
+
`export interface ${name}Actions {`,
|
|
728
|
+
...entries.map(([value, action]) => ` ${action}: (${value}: string) => { ${value}: string }`),
|
|
729
|
+
'}',
|
|
730
|
+
'',
|
|
731
|
+
`export type ${name}Type = MakeLogicType<${name}Values, ${name}Actions>`,
|
|
732
|
+
'',
|
|
733
|
+
`export const ${name} = kea<${name}Type>({`,
|
|
734
|
+
' actions: () => ({',
|
|
735
|
+
...entries.map(([value, action]) => ` ${action}: (${value}: string) => ({ ${value} }),`),
|
|
736
|
+
' }),',
|
|
737
|
+
' reducers: () => ({',
|
|
738
|
+
...entries.map(([value, action]) => ` ${value}: ['', { ${action}: (_, p) => p.${value} }],`),
|
|
739
|
+
' }),',
|
|
740
|
+
'})',
|
|
741
|
+
'',
|
|
742
|
+
].join('\n')
|
|
743
|
+
|
|
744
|
+
fs.writeFileSync(
|
|
745
|
+
path.join(rootDir, 'alphaLogic.ts'),
|
|
746
|
+
makeProvider('alphaLogic', [
|
|
747
|
+
['bravo', 'setBravo'],
|
|
748
|
+
['alpha', 'setAlpha'],
|
|
749
|
+
]),
|
|
750
|
+
)
|
|
751
|
+
fs.writeFileSync(path.join(rootDir, 'betaLogic.ts'), makeProvider('betaLogic', [['charlie', 'setCharlie']]))
|
|
752
|
+
|
|
753
|
+
const consumerDir = path.join(rootDir, 'consumer')
|
|
754
|
+
const consumerLogicPath = path.join(consumerDir, 'sortedLogic.ts')
|
|
755
|
+
fs.mkdirSync(consumerDir, { recursive: true })
|
|
756
|
+
fs.writeFileSync(
|
|
757
|
+
consumerLogicPath,
|
|
758
|
+
[
|
|
759
|
+
"import { kea } from 'kea'",
|
|
760
|
+
'',
|
|
761
|
+
"import { alphaLogic } from '../alphaLogic'",
|
|
762
|
+
"import { betaLogic } from '../betaLogic'",
|
|
763
|
+
'',
|
|
764
|
+
'export const sortedLogic = kea({',
|
|
765
|
+
' connect: {',
|
|
766
|
+
" actions: [betaLogic, ['setCharlie'], alphaLogic, ['setBravo', 'setAlpha']],",
|
|
767
|
+
" values: [betaLogic, ['charlie'], alphaLogic, ['bravo', 'alpha']],",
|
|
768
|
+
' },',
|
|
769
|
+
' actions: () => ({',
|
|
770
|
+
' zulu: (z: number) => ({ z }),',
|
|
771
|
+
' aardvark: (a: number) => ({ a }),',
|
|
772
|
+
' }),',
|
|
773
|
+
' reducers: () => ({',
|
|
774
|
+
' zebra: [0, { zulu: (_, { z }) => z }],',
|
|
775
|
+
' anteater: [0, { aardvark: (_, { a }) => a }],',
|
|
776
|
+
' }),',
|
|
777
|
+
'})',
|
|
778
|
+
'',
|
|
779
|
+
].join('\n'),
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
const appOptions: AppOptions = {
|
|
783
|
+
rootPath: rootDir,
|
|
784
|
+
typesPath: rootDir,
|
|
785
|
+
packageJsonPath: path.join(tempDir, 'package.json'),
|
|
786
|
+
inlinePaths: [consumerDir],
|
|
787
|
+
write: true,
|
|
788
|
+
log: () => {},
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const program = createProgram([
|
|
792
|
+
path.join(rootDir, 'alphaLogic.ts'),
|
|
793
|
+
path.join(rootDir, 'betaLogic.ts'),
|
|
794
|
+
consumerLogicPath,
|
|
795
|
+
])
|
|
796
|
+
await printToFiles(program, appOptions, visitProgram(program, appOptions))
|
|
797
|
+
|
|
798
|
+
const written = fs.readFileSync(consumerLogicPath, 'utf8')
|
|
799
|
+
const orderOf = (names: string[]): void => {
|
|
800
|
+
const positions = names.map((name) => written.indexOf(name))
|
|
801
|
+
positions.forEach((position, index) => {
|
|
802
|
+
expect(position).toBeGreaterThan(-1)
|
|
803
|
+
if (index > 0) {
|
|
804
|
+
expect(position).toBeGreaterThan(positions[index - 1])
|
|
805
|
+
}
|
|
806
|
+
})
|
|
807
|
+
}
|
|
808
|
+
// values: alphaLogic group (alphabetical), betaLogic group, then own values alphabetically
|
|
809
|
+
orderOf(['alpha: string', 'bravo: string', 'charlie: string', 'anteater: number', 'zebra: number'])
|
|
810
|
+
// actions: same grouping
|
|
811
|
+
orderOf(['setAlpha:', 'setBravo:', 'setCharlie:', 'aardvark:', 'zulu:'])
|
|
812
|
+
} finally {
|
|
813
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
814
|
+
}
|
|
815
|
+
})
|
|
816
|
+
|
|
817
|
+
test('files that already carry an inline block stay inline without any inline options', async () => {
|
|
818
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kea-typegen-inline-sticky-'))
|
|
819
|
+
|
|
820
|
+
try {
|
|
821
|
+
const rootDir = path.join(tempDir, 'src')
|
|
822
|
+
const logicPath = path.join(rootDir, 'stickyLogic.ts')
|
|
823
|
+
|
|
824
|
+
writeKeaDts(tempDir)
|
|
825
|
+
fs.mkdirSync(rootDir, { recursive: true })
|
|
826
|
+
fs.writeFileSync(
|
|
827
|
+
logicPath,
|
|
828
|
+
[
|
|
829
|
+
"import { MakeLogicType, kea } from 'kea'",
|
|
830
|
+
'',
|
|
831
|
+
'// Generated by kea-typegen. DO NOT EDIT THIS BLOCK MANUALLY.',
|
|
832
|
+
'export interface stickyLogicActions {',
|
|
833
|
+
' setValue: (value: string) => {',
|
|
834
|
+
' value: string',
|
|
835
|
+
' }',
|
|
836
|
+
'}',
|
|
837
|
+
'',
|
|
838
|
+
'export type stickyLogicType = MakeLogicType<{}, stickyLogicActions>',
|
|
839
|
+
'',
|
|
840
|
+
'export const stickyLogic = kea<stickyLogicType>({',
|
|
841
|
+
' actions: () => ({',
|
|
842
|
+
' setValue: (value: string) => ({ value }),',
|
|
843
|
+
' setOther: (other: number) => ({ other }),',
|
|
844
|
+
' }),',
|
|
845
|
+
'})',
|
|
846
|
+
'',
|
|
847
|
+
].join('\n'),
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
// no `inline`, no `inlinePaths` - the existing block alone keeps the file inline
|
|
851
|
+
const appOptions: AppOptions = {
|
|
852
|
+
rootPath: rootDir,
|
|
853
|
+
typesPath: rootDir,
|
|
854
|
+
packageJsonPath: path.join(tempDir, 'package.json'),
|
|
855
|
+
write: true,
|
|
856
|
+
log: () => {},
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const program = createProgram([logicPath])
|
|
860
|
+
const response = await printToFiles(program, appOptions, visitProgram(program, appOptions))
|
|
861
|
+
expect(response.writtenFiles).toBe(1)
|
|
862
|
+
|
|
863
|
+
const writtenLogic = fs.readFileSync(logicPath, 'utf8')
|
|
864
|
+
expect(writtenLogic).toContain('setOther: (other: number) =>')
|
|
865
|
+
expect(writtenLogic.match(/Update if you're an agent, ignore if you're human/g)?.length).toBe(1)
|
|
866
|
+
expect(fs.existsSync(path.join(rootDir, 'stickyLogicType.ts'))).toBe(false)
|
|
867
|
+
} finally {
|
|
868
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
869
|
+
}
|
|
870
|
+
})
|
|
266
871
|
})
|
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
|
+
}
|
package/src/cli/typegen.ts
CHANGED
|
@@ -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',
|