pastoria 1.0.12 → 1.0.14

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/src/generate.ts CHANGED
@@ -31,15 +31,14 @@ import * as path from 'node:path';
31
31
  import pc from 'picocolors';
32
32
  import {
33
33
  CodeBlockWriter,
34
- IndentationText,
35
34
  Project,
36
35
  SourceFile,
37
36
  Symbol,
38
37
  SyntaxKind,
39
38
  ts,
40
39
  TypeFlags,
41
- WriterFunction,
42
40
  } from 'ts-morph';
41
+ import {logInfo, logWarn} from './logger.js';
43
42
 
44
43
  async function loadRouterTemplates(project: Project, filename: string) {
45
44
  async function loadSourceFile(fileName: string, templateFileName: string) {
@@ -90,6 +89,36 @@ export interface PastoriaMetadata {
90
89
  export const PASTORIA_TAG_REGEX =
91
90
  /@(route|resource|appRoot|param|gqlContext|serverRoute)\b/;
92
91
 
92
+ function collectQueryParameters(
93
+ project: Project,
94
+ queries: string[],
95
+ ): Map<string, ts.Type> {
96
+ const vars = new Map<string, ts.Type>();
97
+
98
+ for (const query of queries) {
99
+ const variablesType = project
100
+ .getSourceFile(`__generated__/queries/${query}.graphql.ts`)
101
+ ?.getExportedDeclarations()
102
+ .get(`${query}$variables`)
103
+ ?.at(0)
104
+ ?.getType();
105
+
106
+ if (variablesType == null) continue;
107
+
108
+ for (const property of variablesType.getProperties()) {
109
+ // TODO: Detect conflicting types among properties declared.
110
+ const propertyName = property.getName();
111
+ const propertyType = property.getValueDeclaration()?.getType();
112
+
113
+ if (propertyType) {
114
+ vars.set(propertyName, propertyType.compilerType);
115
+ }
116
+ }
117
+ }
118
+
119
+ return vars;
120
+ }
121
+
93
122
  function collectPastoriaMetadata(project: Project): PastoriaMetadata {
94
123
  const resources = new Map<string, RouterResource>();
95
124
  const routes = new Map<string, RouterRoute>();
@@ -158,10 +187,7 @@ function collectPastoriaMetadata(project: Project): PastoriaMetadata {
158
187
  switch (tag.tagName.getText()) {
159
188
  case 'appRoot': {
160
189
  if (appRoot != null) {
161
- console.warn(
162
- pc.yellow('Warning:'),
163
- 'Multiple @appRoot tags found. Using the first one.',
164
- );
190
+ logWarn('Multiple @appRoot tags found. Using the first one.');
165
191
  } else {
166
192
  appRoot = {
167
193
  sourceFile,
@@ -195,8 +221,7 @@ function collectPastoriaMetadata(project: Project): PastoriaMetadata {
195
221
 
196
222
  if (extendsPastoriaRootContext) {
197
223
  if (gqlContext != null) {
198
- console.warn(
199
- pc.yellow('Warning:'),
224
+ logWarn(
200
225
  'Multiple classes with @gqlContext extending PastoriaRootContext found. Using the first one.',
201
226
  );
202
227
  } else {
@@ -284,23 +309,32 @@ function getResourceQueriesAndEntryPoints(symbol: Symbol): {
284
309
 
285
310
  entryPoints?.getProperties().forEach((prop) => {
286
311
  const epRef = prop.getName();
287
- const resourceName = prop
312
+ const entryPointTypeRef = prop
288
313
  .getValueDeclaration()
289
314
  ?.asKind(SyntaxKind.PropertySignature)
290
315
  ?.getTypeNode()
291
- ?.asKind(SyntaxKind.TypeReference)
292
- ?.getTypeArguments()
293
- .at(0)
294
- ?.asKind(SyntaxKind.TypeReference)
295
- ?.getTypeArguments()
296
- .at(0)
297
- ?.asKind(SyntaxKind.LiteralType)
298
- ?.getLiteral()
299
- .asKind(SyntaxKind.StringLiteral)
300
- ?.getLiteralText();
301
-
302
- if (resourceName) {
303
- resource.entryPoints.set(epRef, resourceName);
316
+ ?.asKind(SyntaxKind.TypeReference);
317
+
318
+ const entryPointTypeName = entryPointTypeRef?.getTypeName().getText();
319
+ if (entryPointTypeName !== 'EntryPoint') {
320
+ // TODO: Warn about found types not named EntryPoint
321
+ return;
322
+ }
323
+
324
+ const entryPointInner = entryPointTypeRef?.getTypeArguments().at(0);
325
+ const moduleTypeRef = entryPointInner?.asKind(SyntaxKind.TypeReference);
326
+ if (moduleTypeRef != null) {
327
+ const resourceName = moduleTypeRef
328
+ ?.getTypeArguments()
329
+ .at(0)
330
+ ?.asKind(SyntaxKind.LiteralType)
331
+ ?.getLiteral()
332
+ .asKind(SyntaxKind.StringLiteral)
333
+ ?.getLiteralText();
334
+
335
+ if (resourceName) {
336
+ resource.entryPoints.set(epRef, resourceName);
337
+ }
304
338
  }
305
339
  });
306
340
  }
@@ -308,41 +342,62 @@ function getResourceQueriesAndEntryPoints(symbol: Symbol): {
308
342
  return resource;
309
343
  }
310
344
 
311
- function zodSchemaOfType(tc: ts.TypeChecker, t: ts.Type): string {
312
- if (t.getFlags() & TypeFlags.String) {
345
+ function zodSchemaOfType(
346
+ sf: SourceFile,
347
+ tc: ts.TypeChecker,
348
+ t: ts.Type,
349
+ ): string {
350
+ if (t.aliasSymbol) {
351
+ const decl = t.aliasSymbol.declarations?.at(0);
352
+ if (decl == null) {
353
+ logWarn('Could not handle type:', tc.typeToString(t));
354
+ return `z.any()`;
355
+ } else {
356
+ const importPath = sf.getRelativePathAsModuleSpecifierTo(
357
+ decl.getSourceFile().fileName,
358
+ );
359
+
360
+ return `z.transform((s: string) => s as import('${importPath}').${t.aliasSymbol.getName()})`;
361
+ }
362
+ } else if (t.getFlags() & TypeFlags.String) {
313
363
  return `z.pipe(z.string(), z.transform(decodeURIComponent))`;
314
364
  } else if (t.getFlags() & TypeFlags.Number) {
315
365
  return `z.coerce.number<number>()`;
316
366
  } else if (t.getFlags() & TypeFlags.Null) {
317
367
  return `z.preprocess(s => s == null ? undefined : s, z.undefined())`;
318
368
  } else if (t.isUnion()) {
319
- const isRepresentingOptional =
320
- t.types.length === 2 &&
321
- t.types.some((s) => s.getFlags() & TypeFlags.Null);
322
-
323
- if (isRepresentingOptional) {
324
- const nonOptionalType = t.types.find(
325
- (s) => !(s.getFlags() & TypeFlags.Null),
326
- )!;
369
+ const nullishTypes: ts.Type[] = [];
370
+ const nonNullishTypes: ts.Type[] = [];
371
+ for (const s of t.types) {
372
+ const flags = s.getFlags();
373
+ if (flags & TypeFlags.Null || flags & TypeFlags.Undefined) {
374
+ nullishTypes.push(s);
375
+ } else {
376
+ nonNullishTypes.push(s);
377
+ }
378
+ }
327
379
 
328
- return `z.pipe(z.nullish(${zodSchemaOfType(tc, nonOptionalType)}), z.transform(s => s == null ? undefined : s))`;
380
+ if (nullishTypes.length > 0 && nonNullishTypes.length > 0) {
381
+ const nonOptionalType = t.getNonNullableType();
382
+ return `z.pipe(z.nullish(${zodSchemaOfType(sf, tc, nonOptionalType)}), z.transform(s => s == null ? undefined : s))`;
329
383
  } else {
330
- return `z.union([${t.types.map((it) => zodSchemaOfType(tc, it)).join(', ')}])`;
384
+ return `z.union([${t.types.map((it) => zodSchemaOfType(sf, tc, it)).join(', ')}])`;
331
385
  }
332
386
  } else if (tc.isArrayLikeType(t)) {
333
387
  const typeArg = tc.getTypeArguments(t as ts.TypeReference)[0];
334
388
  const argZodSchema =
335
- typeArg == null ? `z.any()` : zodSchemaOfType(tc, typeArg);
389
+ typeArg == null ? `z.any()` : zodSchemaOfType(sf, tc, typeArg);
336
390
 
337
391
  return `z.array(${argZodSchema})`;
338
392
  } else {
339
- console.log('Could not handle type:', tc.typeToString(t));
393
+ logWarn('Could not handle type:', tc.typeToString(t));
340
394
  return `z.any()`;
341
395
  }
342
396
  }
343
397
 
344
398
  function writeEntryPoint(
345
399
  writer: CodeBlockWriter,
400
+ project: Project,
346
401
  metadata: PastoriaMetadata,
347
402
  consumedQueries: Set<string>,
348
403
  resourceName: string,
@@ -350,6 +405,7 @@ function writeEntryPoint(
350
405
  parseVars = true,
351
406
  ) {
352
407
  writer.writeLine(`root: JSResource.fromModuleId('${resourceName}'),`);
408
+
353
409
  writer
354
410
  .write(`getPreloadProps(${parseVars ? '{params, schema}' : ''})`)
355
411
  .block(() => {
@@ -364,11 +420,28 @@ function writeEntryPoint(
364
420
  for (const [queryRef, query] of resource.queries.entries()) {
365
421
  consumedQueries.add(query);
366
422
 
423
+ // Determine which variables this specific query needs
424
+ const queryVars = collectQueryParameters(project, [query]);
425
+ const hasVariables = queryVars.size > 0;
426
+
367
427
  writer
368
428
  .write(`${queryRef}:`)
369
429
  .block(() => {
370
430
  writer.writeLine(`parameters: ${query}Parameters,`);
371
- writer.writeLine(`variables`);
431
+
432
+ if (hasVariables) {
433
+ const varNames = Array.from(queryVars.keys());
434
+ // Always pick from the variables object
435
+ writer.write(`variables: {`);
436
+ writer.write(
437
+ varNames.map((v) => `${v}: variables.${v}`).join(', '),
438
+ );
439
+ writer.write(`}`);
440
+ } else {
441
+ // Query has no variables, pass empty object
442
+ writer.write(`variables: {}`);
443
+ }
444
+ writer.newLine();
372
445
  })
373
446
  .write(',');
374
447
  }
@@ -382,19 +455,23 @@ function writeEntryPoint(
382
455
  ] of resource.entryPoints.entries()) {
383
456
  const subresource = metadata.resources.get(subresourceName);
384
457
  if (subresource) {
385
- writer.write(`${epRef}:`).block(() => {
386
- writer.writeLine(`entryPointParams: {},`);
387
- writer.write('entryPoint:').block(() => {
388
- writeEntryPoint(
389
- writer,
390
- metadata,
391
- consumedQueries,
392
- subresourceName,
393
- subresource,
394
- false,
395
- );
396
- });
397
- });
458
+ writer
459
+ .write(`${epRef}:`)
460
+ .block(() => {
461
+ writer.writeLine(`entryPointParams: {},`);
462
+ writer.write('entryPoint:').block(() => {
463
+ writeEntryPoint(
464
+ writer,
465
+ project,
466
+ metadata,
467
+ consumedQueries,
468
+ subresourceName,
469
+ subresource,
470
+ false,
471
+ );
472
+ });
473
+ })
474
+ .writeLine(',');
398
475
  }
399
476
  }
400
477
  });
@@ -415,7 +492,7 @@ async function generateRouter(project: Project, metadata: PastoriaMetadata) {
415
492
  routerConf.getPropertyOrThrow('noop').remove();
416
493
 
417
494
  let entryPointImportIndex = 0;
418
- for (const [
495
+ for (let [
419
496
  routeName,
420
497
  {sourceFile, symbol, params},
421
498
  ] of metadata.routes.entries()) {
@@ -448,6 +525,7 @@ async function generateRouter(project: Project, metadata: PastoriaMetadata) {
448
525
  writer.write('return ').block(() => {
449
526
  writeEntryPoint(
450
527
  writer,
528
+ project,
451
529
  metadata,
452
530
  consumedQueries,
453
531
  resourceName,
@@ -457,6 +535,10 @@ async function generateRouter(project: Project, metadata: PastoriaMetadata) {
457
535
  },
458
536
  });
459
537
 
538
+ if (params.size === 0 && consumedQueries.size > 0) {
539
+ params = collectQueryParameters(project, Array.from(consumedQueries));
540
+ }
541
+
460
542
  for (const query of consumedQueries) {
461
543
  routerTemplate.addImportDeclaration({
462
544
  moduleSpecifier: `#genfiles/queries/${query}$parameters`,
@@ -497,7 +579,7 @@ async function generateRouter(project: Project, metadata: PastoriaMetadata) {
497
579
  writer.writeLine(`schema: z.object({`);
498
580
  for (const [paramName, paramType] of Array.from(params)) {
499
581
  writer.writeLine(
500
- ` ${paramName}: ${zodSchemaOfType(tc, paramType)},`,
582
+ ` ${paramName}: ${zodSchemaOfType(routerTemplate, tc, paramType)},`,
501
583
  );
502
584
  }
503
585
 
@@ -508,7 +590,7 @@ async function generateRouter(project: Project, metadata: PastoriaMetadata) {
508
590
  },
509
591
  });
510
592
 
511
- console.log(
593
+ logInfo(
512
594
  'Created route',
513
595
  pc.cyan(routeName),
514
596
  'for',
@@ -559,7 +641,7 @@ async function generateJsResource(
559
641
  },
560
642
  });
561
643
 
562
- console.log(
644
+ logInfo(
563
645
  'Created resource',
564
646
  pc.cyan(resourceName),
565
647
  'for',
@@ -607,7 +689,7 @@ export {${appRootSymbol.getName()} as App} from '${moduleSpecifier}';
607
689
 
608
690
  await appRootFile.save();
609
691
 
610
- console.log(
692
+ logInfo(
611
693
  'Created app root for',
612
694
  pc.green(appRootSymbol.getName()),
613
695
  'exported from',
@@ -643,7 +725,7 @@ async function generateGraphqlContext(
643
725
  export {${contextSymbol.getName()} as Context} from '${moduleSpecifier}';
644
726
  `);
645
727
 
646
- console.log(
728
+ logInfo(
647
729
  'Created GraphQL context for',
648
730
  pc.green(contextSymbol.getName()),
649
731
  'exported from',
@@ -663,10 +745,7 @@ import {PastoriaRootContext} from 'pastoria-runtime/server';
663
745
  export class Context extends PastoriaRootContext {}
664
746
  `);
665
747
 
666
- console.log(
667
- 'No @gqlContext found, generating default',
668
- pc.green('Context'),
669
- );
748
+ logInfo('No @gqlContext found, generating default', pc.green('Context'));
670
749
  }
671
750
 
672
751
  await contextFile.save();
@@ -715,7 +794,7 @@ export const router = express.Router();
715
794
  `router.use('${routeName}', ${importAlias})`,
716
795
  );
717
796
 
718
- console.log(
797
+ logInfo(
719
798
  'Created server handler',
720
799
  pc.cyan(routeName),
721
800
  'for',
@@ -744,3 +823,96 @@ export async function generatePastoriaArtifacts(
744
823
  await generateJsResource(project, metadata);
745
824
  await generateServerHandler(project, metadata);
746
825
  }
826
+
827
+ export interface PastoriaCapabilities {
828
+ hasAppRoot: boolean;
829
+ hasServerHandler: boolean;
830
+ }
831
+
832
+ export function generateClientEntry({
833
+ hasAppRoot,
834
+ }: PastoriaCapabilities): string {
835
+ const appImport = hasAppRoot
836
+ ? `import {App} from '#genfiles/router/app_root';`
837
+ : '';
838
+ const appValue = hasAppRoot ? 'App' : 'null';
839
+
840
+ return `// Generated by Pastoria.
841
+ import {createRouterApp} from '#genfiles/router/router';
842
+ ${appImport}
843
+ import {hydrateRoot} from 'react-dom/client';
844
+
845
+ async function main() {
846
+ const RouterApp = await createRouterApp();
847
+ hydrateRoot(document, <RouterApp App={${appValue}} />);
848
+ }
849
+
850
+ main();
851
+ `;
852
+ }
853
+
854
+ export function generateServerEntry({
855
+ hasAppRoot,
856
+ hasServerHandler,
857
+ }: PastoriaCapabilities): string {
858
+ const appImport = hasAppRoot
859
+ ? `import {App} from '#genfiles/router/app_root';`
860
+ : '';
861
+ const appValue = hasAppRoot ? 'App' : 'null';
862
+
863
+ const serverHandlerImport = hasServerHandler
864
+ ? `import {router as serverHandler} from '#genfiles/router/server_handler';`
865
+ : '';
866
+ const serverHandlerUse = hasServerHandler
867
+ ? ' router.use(serverHandler)'
868
+ : '';
869
+
870
+ return `// Generated by Pastoria.
871
+ import {JSResource} from '#genfiles/router/js_resource';
872
+ import {
873
+ listRoutes,
874
+ router__createAppFromEntryPoint,
875
+ router__loadEntryPoint,
876
+ } from '#genfiles/router/router';
877
+ import {getSchema} from '#genfiles/schema/schema';
878
+ import {Context} from '#genfiles/router/context';
879
+ ${appImport}
880
+ ${serverHandlerImport}
881
+ import express from 'express';
882
+ import {GraphQLSchema, specifiedDirectives} from 'graphql';
883
+ import {PastoriaConfig} from 'pastoria-config';
884
+ import {createRouterHandler} from 'pastoria-runtime/server';
885
+ import type {Manifest} from 'vite';
886
+
887
+ const schemaConfig = getSchema().toConfig();
888
+ const schema = new GraphQLSchema({
889
+ ...schemaConfig,
890
+ directives: [...specifiedDirectives, ...schemaConfig.directives],
891
+ });
892
+
893
+ export function createHandler(
894
+ persistedQueries: Record<string, string>,
895
+ config: Required<PastoriaConfig>,
896
+ manifest?: Manifest,
897
+ ) {
898
+ const routeHandler = createRouterHandler(
899
+ listRoutes(),
900
+ JSResource.srcOfModuleId,
901
+ router__loadEntryPoint,
902
+ router__createAppFromEntryPoint,
903
+ ${appValue},
904
+ schema,
905
+ (req) => Context.createFromRequest(req),
906
+ persistedQueries,
907
+ config,
908
+ manifest,
909
+ );
910
+
911
+ const router = express.Router();
912
+ router.use(routeHandler);
913
+ ${serverHandlerUse}
914
+
915
+ return router;
916
+ }
917
+ `;
918
+ }
package/src/index.ts CHANGED
@@ -30,7 +30,7 @@ async function main() {
30
30
  'Specific build steps to run (schema, relay, router). If not provided, will infer from changed files.',
31
31
  )
32
32
  .option('-B, --always-make', 'Always make, never cache')
33
- .option('--release', 'Build for production')
33
+ .option('-R, --release', 'Build for production')
34
34
  .option('-w, --watch', 'Watch for changes and rebuild')
35
35
  .action(createBuild);
36
36
 
package/src/logger.ts ADDED
@@ -0,0 +1,12 @@
1
+ import {createLogger} from 'vite';
2
+ import pc from 'picocolors';
3
+
4
+ export const logger = createLogger('info', {prefix: pc.greenBright('[MAKE]')});
5
+
6
+ export function logInfo(...messages: string[]) {
7
+ logger.info(messages.join(' '), {timestamp: true});
8
+ }
9
+
10
+ export function logWarn(...messages: string[]) {
11
+ logger.warn(messages.join(' '), {timestamp: true});
12
+ }
@@ -0,0 +1,109 @@
1
+ import tailwindcss from '@tailwindcss/vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import {access} from 'node:fs/promises';
4
+ import {
5
+ InlineConfig,
6
+ PluginOption,
7
+ type BuildEnvironmentOptions,
8
+ type Plugin,
9
+ } from 'vite';
10
+ import {cjsInterop} from 'vite-plugin-cjs-interop';
11
+ import {
12
+ generateClientEntry,
13
+ generateServerEntry,
14
+ PastoriaCapabilities,
15
+ } from './generate.js';
16
+ import {logger} from './logger.js';
17
+
18
+ async function determineCapabilities(): Promise<PastoriaCapabilities> {
19
+ const capabilities: PastoriaCapabilities = {
20
+ hasAppRoot: false,
21
+ hasServerHandler: false,
22
+ };
23
+
24
+ async function hasAppRoot() {
25
+ try {
26
+ await access('__generated__/router/app_root.ts');
27
+ capabilities.hasAppRoot = true;
28
+ } catch {}
29
+ }
30
+
31
+ async function hasServerHandler() {
32
+ try {
33
+ await access('__generated__/router/server_handler.ts');
34
+ capabilities.hasServerHandler = true;
35
+ } catch {}
36
+ }
37
+
38
+ await Promise.all([hasAppRoot(), hasServerHandler()]);
39
+ return capabilities;
40
+ }
41
+
42
+ function pastoriaEntryPlugin(): Plugin {
43
+ const clientEntryModuleId = 'virtual:pastoria-entry-client.tsx';
44
+ const serverEntryModuleId = 'virtual:pastoria-entry-server.tsx';
45
+
46
+ return {
47
+ name: 'pastoria-entry',
48
+ resolveId(id) {
49
+ if (id === clientEntryModuleId) {
50
+ return clientEntryModuleId; // Return without \0 prefix so React plugin can see .tsx extension
51
+ } else if (id === serverEntryModuleId) {
52
+ return serverEntryModuleId;
53
+ }
54
+ },
55
+ async load(id) {
56
+ const capabilities = await determineCapabilities();
57
+ if (id === clientEntryModuleId) {
58
+ return generateClientEntry(capabilities);
59
+ } else if (id === serverEntryModuleId) {
60
+ return generateServerEntry(capabilities);
61
+ }
62
+ },
63
+ };
64
+ }
65
+
66
+ export const CLIENT_BUILD: BuildEnvironmentOptions = {
67
+ outDir: 'dist/client',
68
+ rollupOptions: {
69
+ input: 'virtual:pastoria-entry-client.tsx',
70
+ },
71
+ };
72
+
73
+ export const SERVER_BUILD: BuildEnvironmentOptions = {
74
+ outDir: 'dist/server',
75
+ ssr: true,
76
+ rollupOptions: {
77
+ input: 'virtual:pastoria-entry-server.tsx',
78
+ },
79
+ };
80
+
81
+ export function createBuildConfig(
82
+ buildEnv: BuildEnvironmentOptions,
83
+ ): InlineConfig {
84
+ return {
85
+ appType: 'custom' as const,
86
+ customLogger: logger,
87
+ build: {
88
+ ...buildEnv,
89
+ assetsInlineLimit: 0,
90
+ manifest: true,
91
+ ssrManifest: true,
92
+ },
93
+ plugins: [
94
+ pastoriaEntryPlugin(),
95
+ tailwindcss() as PluginOption,
96
+ react({
97
+ babel: {
98
+ plugins: [['babel-plugin-react-compiler', {}], 'relay'],
99
+ },
100
+ }),
101
+ cjsInterop({
102
+ dependencies: ['react-relay', 'react-relay/hooks', 'relay-runtime'],
103
+ }),
104
+ ],
105
+ ssr: {
106
+ noExternal: ['pastoria-runtime'],
107
+ },
108
+ };
109
+ }
@@ -245,20 +245,7 @@ export function router__createAppFromEntryPoint(
245
245
 
246
246
  return (
247
247
  <RouterContext value={routerContextValue}>
248
- {'fallback' in entryPoint.entryPoints ? (
249
- <Suspense
250
- fallback={
251
- <EntryPointContainer
252
- entryPointReference={entryPoint.entryPoints.fallback}
253
- props={{}}
254
- />
255
- }
256
- >
257
- <EntryPointContainer entryPointReference={entryPoint} props={{}} />
258
- </Suspense>
259
- ) : (
260
- <EntryPointContainer entryPointReference={entryPoint} props={{}} />
261
- )}
248
+ <EntryPointContainer entryPointReference={entryPoint} props={{}} />
262
249
  </RouterContext>
263
250
  );
264
251
  }
package/tsconfig.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
4
  "module": "nodenext",
5
- "lib": ["ES2022", "DOM"],
5
+ "lib": ["ES2022", "DOM", "ESNext.Collection"],
6
6
  "moduleResolution": "nodenext",
7
7
  "outDir": "./dist",
8
8
  "rootDir": "./src",