pastoria 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +44 -17
- package/dist/build.js.map +1 -1
- package/dist/generate.d.ts +1 -0
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +254 -117
- package/dist/generate.js.map +1 -1
- package/package.json +3 -3
- package/src/build.ts +55 -20
- package/src/generate.ts +408 -168
package/src/generate.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* - Add @resource <resource-name> to exports for lazy loading
|
|
21
21
|
* - Add @appRoot to a component to designate it as the application root wrapper
|
|
22
22
|
* - Add @gqlContext to a class extending PastoriaRootContext to provide a custom GraphQL context
|
|
23
|
+
* - Add @serverRoute to functions to add an express handler
|
|
23
24
|
*
|
|
24
25
|
* The generator automatically creates Zod schemas for route parameters based on
|
|
25
26
|
* TypeScript types, enabling runtime validation and type safety.
|
|
@@ -27,25 +28,20 @@
|
|
|
27
28
|
|
|
28
29
|
import {readFile} from 'node:fs/promises';
|
|
29
30
|
import * as path from 'node:path';
|
|
30
|
-
import
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
const APP_ROOT_FILENAME = '__generated__/router/app_root.ts';
|
|
46
|
-
const CONTEXT_FILENAME = '__generated__/router/context.ts';
|
|
47
|
-
|
|
48
|
-
async function loadRouterFiles(project: Project) {
|
|
31
|
+
import pc from 'picocolors';
|
|
32
|
+
import {
|
|
33
|
+
CodeBlockWriter,
|
|
34
|
+
IndentationText,
|
|
35
|
+
Project,
|
|
36
|
+
SourceFile,
|
|
37
|
+
Symbol,
|
|
38
|
+
SyntaxKind,
|
|
39
|
+
ts,
|
|
40
|
+
TypeFlags,
|
|
41
|
+
WriterFunction,
|
|
42
|
+
} from 'ts-morph';
|
|
43
|
+
|
|
44
|
+
async function loadRouterTemplates(project: Project, filename: string) {
|
|
49
45
|
async function loadSourceFile(fileName: string, templateFileName: string) {
|
|
50
46
|
const template = await readFile(templateFileName, 'utf-8');
|
|
51
47
|
const warningComment = `/*
|
|
@@ -59,50 +55,51 @@ async function loadRouterFiles(project: Project) {
|
|
|
59
55
|
});
|
|
60
56
|
}
|
|
61
57
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
]);
|
|
66
|
-
|
|
67
|
-
return {jsResource, router} as const;
|
|
58
|
+
const template = path.join(import.meta.dirname, '../templates', filename);
|
|
59
|
+
const output = path.join('__generated__/router', filename);
|
|
60
|
+
return loadSourceFile(output, template);
|
|
68
61
|
}
|
|
69
62
|
|
|
70
63
|
type RouterResource = {
|
|
71
|
-
resourceName: string;
|
|
72
64
|
sourceFile: SourceFile;
|
|
73
65
|
symbol: Symbol;
|
|
66
|
+
queries: Map<string, string>;
|
|
67
|
+
entryPoints: Map<string, string>;
|
|
74
68
|
};
|
|
75
69
|
|
|
76
70
|
type RouterRoute = {
|
|
77
|
-
routeName: string;
|
|
78
71
|
sourceFile: SourceFile;
|
|
79
72
|
symbol: Symbol;
|
|
80
73
|
params: Map<string, ts.Type>;
|
|
81
74
|
};
|
|
82
75
|
|
|
83
|
-
type
|
|
76
|
+
type ExportedSymbol = {
|
|
84
77
|
sourceFile: SourceFile;
|
|
85
78
|
symbol: Symbol;
|
|
86
79
|
};
|
|
87
80
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
interface PastoriaMetadata {
|
|
82
|
+
resources: Map<string, RouterResource>;
|
|
83
|
+
routes: Map<string, RouterRoute>;
|
|
84
|
+
serverHandlers: Map<string, ExportedSymbol>;
|
|
85
|
+
appRoot: ExportedSymbol | null;
|
|
86
|
+
gqlContext: ExportedSymbol | null;
|
|
87
|
+
}
|
|
92
88
|
|
|
93
89
|
// Regex to quickly check if a file contains any Pastoria JSDoc tags
|
|
94
|
-
const PASTORIA_TAG_REGEX =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const resources
|
|
103
|
-
const routes
|
|
104
|
-
|
|
105
|
-
let
|
|
90
|
+
const PASTORIA_TAG_REGEX =
|
|
91
|
+
/@(route|resource|appRoot|param|gqlContext|serverRoute)\b/;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
*
|
|
95
|
+
* @query {abc} 123
|
|
96
|
+
*/
|
|
97
|
+
function collectPastoriaMetadata(project: Project): PastoriaMetadata {
|
|
98
|
+
const resources = new Map<string, RouterResource>();
|
|
99
|
+
const routes = new Map<string, RouterRoute>();
|
|
100
|
+
const serverHandlers = new Map<string, ExportedSymbol>();
|
|
101
|
+
let appRoot: ExportedSymbol | null = null;
|
|
102
|
+
let gqlContext: ExportedSymbol | null = null;
|
|
106
103
|
|
|
107
104
|
function visitRouterNodes(sourceFile: SourceFile) {
|
|
108
105
|
// Skip generated files
|
|
@@ -117,8 +114,10 @@ function collectRouterNodes(project: Project): {
|
|
|
117
114
|
}
|
|
118
115
|
|
|
119
116
|
sourceFile.getExportSymbols().forEach((symbol) => {
|
|
120
|
-
let routerResource = null as RouterResource | null;
|
|
121
|
-
|
|
117
|
+
let routerResource = null as [string, RouterResource] | null;
|
|
118
|
+
const resourceQueries = new Map<string, string>();
|
|
119
|
+
const resourceEntryPoints = new Map<string, string>();
|
|
120
|
+
let routerRoute = null as [string, RouterRoute] | null;
|
|
122
121
|
const routeParams = new Map<string, ts.Type>();
|
|
123
122
|
|
|
124
123
|
function visitJSDocTags(tag: ts.JSDoc | ts.JSDocTag) {
|
|
@@ -137,25 +136,51 @@ function collectRouterNodes(project: Project): {
|
|
|
137
136
|
} else if (typeof tag.comment === 'string') {
|
|
138
137
|
switch (tag.tagName.getText()) {
|
|
139
138
|
case 'route': {
|
|
140
|
-
routerRoute =
|
|
141
|
-
|
|
142
|
-
sourceFile,
|
|
143
|
-
|
|
144
|
-
params: routeParams,
|
|
145
|
-
};
|
|
139
|
+
routerRoute = [
|
|
140
|
+
tag.comment,
|
|
141
|
+
{sourceFile, symbol, params: routeParams},
|
|
142
|
+
];
|
|
146
143
|
break;
|
|
147
144
|
}
|
|
148
145
|
case 'resource': {
|
|
149
|
-
routerResource =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
146
|
+
routerResource = [
|
|
147
|
+
tag.comment,
|
|
148
|
+
{
|
|
149
|
+
sourceFile,
|
|
150
|
+
symbol,
|
|
151
|
+
queries: resourceQueries,
|
|
152
|
+
entryPoints: resourceEntryPoints,
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'serverRoute': {
|
|
158
|
+
serverHandlers.set(tag.comment, {sourceFile, symbol});
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case 'query': {
|
|
162
|
+
const match = tag.comment.match(
|
|
163
|
+
/^\s*\{\s*(?<query>\w+)\s*\}\s+(?<name>\w+)\s*$/,
|
|
164
|
+
)?.groups;
|
|
165
|
+
|
|
166
|
+
if (match && match.query && match.name) {
|
|
167
|
+
resourceQueries.set(match.name, match.query);
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'entrypoint': {
|
|
172
|
+
const match = tag.comment.match(
|
|
173
|
+
/^\s*\{\s*(?<resource>[\w#]+)\s*\}\s+(?<name>\w+)\s*$/,
|
|
174
|
+
)?.groups;
|
|
175
|
+
|
|
176
|
+
if (match && match.resource && match.name) {
|
|
177
|
+
resourceEntryPoints.set(match.name, match.resource);
|
|
178
|
+
}
|
|
154
179
|
break;
|
|
155
180
|
}
|
|
156
181
|
}
|
|
157
182
|
} else {
|
|
158
|
-
// Handle tags without comments (like @
|
|
183
|
+
// Handle tags without comments (like @ExportedSymbol, @gqlContext)
|
|
159
184
|
switch (tag.tagName.getText()) {
|
|
160
185
|
case 'appRoot': {
|
|
161
186
|
if (appRoot != null) {
|
|
@@ -218,13 +243,18 @@ function collectRouterNodes(project: Project): {
|
|
|
218
243
|
.flatMap((decl) => ts.getJSDocCommentsAndTags(decl.compilerNode))
|
|
219
244
|
.forEach(visitJSDocTags);
|
|
220
245
|
|
|
221
|
-
if (routerRoute != null)
|
|
222
|
-
|
|
246
|
+
if (routerRoute != null) {
|
|
247
|
+
routes.set(routerRoute[0], routerRoute[1]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (routerResource != null) {
|
|
251
|
+
resources.set(routerResource[0], routerResource[1]);
|
|
252
|
+
}
|
|
223
253
|
});
|
|
224
254
|
}
|
|
225
255
|
|
|
226
256
|
project.getSourceFiles().forEach(visitRouterNodes);
|
|
227
|
-
return {resources, routes, appRoot, gqlContext};
|
|
257
|
+
return {resources, routes, appRoot, gqlContext, serverHandlers};
|
|
228
258
|
}
|
|
229
259
|
|
|
230
260
|
function zodSchemaOfType(tc: ts.TypeChecker, t: ts.Type): string {
|
|
@@ -260,53 +290,287 @@ function zodSchemaOfType(tc: ts.TypeChecker, t: ts.Type): string {
|
|
|
260
290
|
}
|
|
261
291
|
}
|
|
262
292
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
293
|
+
function writeEntryPoint(
|
|
294
|
+
writer: CodeBlockWriter,
|
|
295
|
+
metadata: PastoriaMetadata,
|
|
296
|
+
consumedQueries: Set<string>,
|
|
297
|
+
resourceName: string,
|
|
298
|
+
resource: RouterResource,
|
|
299
|
+
parseVars = true,
|
|
300
|
+
) {
|
|
301
|
+
writer.writeLine(`root: JSResource.fromModuleId('${resourceName}'),`);
|
|
302
|
+
writer
|
|
303
|
+
.write(`getPreloadProps(${parseVars ? '{params, schema}' : ''})`)
|
|
304
|
+
.block(() => {
|
|
305
|
+
if (parseVars) {
|
|
306
|
+
writer.writeLine('const variables = schema.parse(params);');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
writer.write('return').block(() => {
|
|
310
|
+
writer
|
|
311
|
+
.write('queries:')
|
|
312
|
+
.block(() => {
|
|
313
|
+
for (const [queryRef, query] of resource.queries.entries()) {
|
|
314
|
+
consumedQueries.add(query);
|
|
315
|
+
|
|
316
|
+
writer
|
|
317
|
+
.write(`${queryRef}:`)
|
|
318
|
+
.block(() => {
|
|
319
|
+
writer.writeLine(`parameters: ${query}Parameters,`);
|
|
320
|
+
writer.writeLine(`variables`);
|
|
321
|
+
})
|
|
322
|
+
.write(',');
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
.writeLine(',');
|
|
326
|
+
|
|
327
|
+
writer.write('entryPoints:').block(() => {
|
|
328
|
+
for (const [
|
|
329
|
+
epRef,
|
|
330
|
+
subresourceName,
|
|
331
|
+
] of resource.entryPoints.entries()) {
|
|
332
|
+
const subresource = metadata.resources.get(subresourceName);
|
|
333
|
+
if (subresource) {
|
|
334
|
+
writer.write(`${epRef}:`).block(() => {
|
|
335
|
+
writer.writeLine(`entryPointParams: {},`);
|
|
336
|
+
writer.write('entryPoint:').block(() => {
|
|
337
|
+
writeEntryPoint(
|
|
338
|
+
writer,
|
|
339
|
+
metadata,
|
|
340
|
+
consumedQueries,
|
|
341
|
+
subresourceName,
|
|
342
|
+
subresource,
|
|
343
|
+
false,
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
}
|
|
268
353
|
|
|
354
|
+
async function generateRouter(project: Project, metadata: PastoriaMetadata) {
|
|
355
|
+
const routerTemplate = await loadRouterTemplates(project, 'router.tsx');
|
|
269
356
|
const tc = project.getTypeChecker().compilerObject;
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
357
|
+
|
|
358
|
+
const routerConf = routerTemplate
|
|
359
|
+
.getVariableDeclarationOrThrow('ROUTER_CONF')
|
|
360
|
+
.getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
|
|
361
|
+
.getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
362
|
+
|
|
363
|
+
routerConf.getPropertyOrThrow('noop').remove();
|
|
364
|
+
|
|
365
|
+
let entryPointImportIndex = 0;
|
|
366
|
+
for (const [
|
|
367
|
+
routeName,
|
|
368
|
+
{sourceFile, symbol, params},
|
|
369
|
+
] of metadata.routes.entries()) {
|
|
370
|
+
const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
|
|
371
|
+
let entryPointExpression: string;
|
|
372
|
+
|
|
373
|
+
// Resource-routes are combined declarations of a resource and a route
|
|
374
|
+
// where we should generate the entrypoint for the route.
|
|
375
|
+
const isResourceRoute = Array.from(metadata.resources.entries()).find(
|
|
376
|
+
([, {symbol: resourceSymbol}]) => symbol === resourceSymbol,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
if (isResourceRoute) {
|
|
380
|
+
const [resourceName, resource] = isResourceRoute;
|
|
381
|
+
const entryPointFunctionName = `entrypoint_${resourceName.replace(/\W/g, '__')}`;
|
|
382
|
+
|
|
383
|
+
routerTemplate.addImportDeclaration({
|
|
384
|
+
moduleSpecifier: './js_resource',
|
|
385
|
+
namedImports: ['JSResource', 'ModuleType'],
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const consumedQueries = new Set<string>();
|
|
389
|
+
routerTemplate.addFunction({
|
|
390
|
+
name: entryPointFunctionName,
|
|
391
|
+
returnType: `EntryPoint<ModuleType<'${resourceName}'>, EntryPointParams<'${routeName}'>>`,
|
|
392
|
+
statements: (writer) => {
|
|
393
|
+
writer.write('return ').block(() => {
|
|
394
|
+
writeEntryPoint(
|
|
395
|
+
writer,
|
|
396
|
+
metadata,
|
|
397
|
+
consumedQueries,
|
|
398
|
+
resourceName,
|
|
399
|
+
resource,
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
for (const query of consumedQueries) {
|
|
406
|
+
routerTemplate.addImportDeclaration({
|
|
407
|
+
moduleSpecifier: `#genfiles/queries/${query}$parameters`,
|
|
408
|
+
defaultImport: `${query}Parameters`,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
entryPointExpression = entryPointFunctionName + '()';
|
|
413
|
+
} else {
|
|
414
|
+
const importAlias = `e${entryPointImportIndex++}`;
|
|
415
|
+
const moduleSpecifier = routerTemplate.getRelativePathAsModuleSpecifierTo(
|
|
416
|
+
sourceFile.getFilePath(),
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
routerTemplate.addImportDeclaration({
|
|
420
|
+
moduleSpecifier,
|
|
421
|
+
namedImports: [
|
|
422
|
+
{
|
|
423
|
+
name: symbol.getName(),
|
|
424
|
+
alias: importAlias,
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
entryPointExpression = importAlias;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
routerConf.addPropertyAssignment({
|
|
433
|
+
name: `"${routeName}"`,
|
|
434
|
+
initializer: (writer) => {
|
|
435
|
+
writer
|
|
436
|
+
.write('{')
|
|
437
|
+
.indent(() => {
|
|
438
|
+
writer.writeLine(`entrypoint: ${entryPointExpression},`);
|
|
439
|
+
if (params.size === 0) {
|
|
440
|
+
writer.writeLine(`schema: z.object({})`);
|
|
441
|
+
} else {
|
|
442
|
+
writer.writeLine(`schema: z.object({`);
|
|
443
|
+
for (const [paramName, paramType] of Array.from(params)) {
|
|
444
|
+
writer.writeLine(
|
|
445
|
+
` ${paramName}: ${zodSchemaOfType(tc, paramType)},`,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
writer.writeLine('})');
|
|
450
|
+
}
|
|
451
|
+
})
|
|
452
|
+
.write('} as const');
|
|
453
|
+
},
|
|
281
454
|
});
|
|
282
455
|
|
|
283
|
-
|
|
284
|
-
|
|
456
|
+
console.log(
|
|
457
|
+
'Created route',
|
|
458
|
+
pc.cyan(routeName),
|
|
459
|
+
'for',
|
|
460
|
+
pc.green(symbol.getName()),
|
|
461
|
+
'exported from',
|
|
462
|
+
pc.yellow(filePath),
|
|
285
463
|
);
|
|
464
|
+
}
|
|
286
465
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
* Do not modify this file directly.
|
|
290
|
-
*/
|
|
466
|
+
await routerTemplate.save();
|
|
467
|
+
}
|
|
291
468
|
|
|
292
|
-
|
|
293
|
-
|
|
469
|
+
async function generateJsResource(
|
|
470
|
+
project: Project,
|
|
471
|
+
metadata: PastoriaMetadata,
|
|
472
|
+
) {
|
|
473
|
+
const jsResourceTemplate = await loadRouterTemplates(
|
|
474
|
+
project,
|
|
475
|
+
'js_resource.ts',
|
|
476
|
+
);
|
|
294
477
|
|
|
295
|
-
|
|
478
|
+
const resourceConf = jsResourceTemplate
|
|
479
|
+
.getVariableDeclarationOrThrow('RESOURCE_CONF')
|
|
480
|
+
.getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
|
|
481
|
+
.getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
482
|
+
|
|
483
|
+
resourceConf.getPropertyOrThrow('noop').remove();
|
|
484
|
+
for (const [
|
|
485
|
+
resourceName,
|
|
486
|
+
{sourceFile, symbol},
|
|
487
|
+
] of metadata.resources.entries()) {
|
|
488
|
+
const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
|
|
489
|
+
const moduleSpecifier =
|
|
490
|
+
jsResourceTemplate.getRelativePathAsModuleSpecifierTo(
|
|
491
|
+
sourceFile.getFilePath(),
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
resourceConf.addPropertyAssignment({
|
|
495
|
+
name: `"${resourceName}"`,
|
|
496
|
+
initializer: (writer) => {
|
|
497
|
+
writer.block(() => {
|
|
498
|
+
writer
|
|
499
|
+
.writeLine(`src: "${filePath}",`)
|
|
500
|
+
.writeLine(
|
|
501
|
+
`loader: () => import("${moduleSpecifier}").then(m => m.${symbol.getName()})`,
|
|
502
|
+
);
|
|
503
|
+
});
|
|
504
|
+
},
|
|
505
|
+
});
|
|
296
506
|
|
|
297
507
|
console.log(
|
|
298
|
-
'Created
|
|
299
|
-
pc.
|
|
508
|
+
'Created resource',
|
|
509
|
+
pc.cyan(resourceName),
|
|
510
|
+
'for',
|
|
511
|
+
pc.green(symbol.getName()),
|
|
300
512
|
'exported from',
|
|
301
513
|
pc.yellow(filePath),
|
|
302
514
|
);
|
|
303
515
|
}
|
|
304
516
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
517
|
+
await jsResourceTemplate.save();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async function generateAppRoot(project: Project, metadata: PastoriaMetadata) {
|
|
521
|
+
const targetDir = process.cwd();
|
|
522
|
+
const appRoot: ExportedSymbol | null = metadata.appRoot;
|
|
523
|
+
|
|
524
|
+
if (appRoot == null) {
|
|
525
|
+
await project
|
|
526
|
+
.getSourceFile('__generated__/router/app_root.ts')
|
|
527
|
+
?.deleteImmediately();
|
|
528
|
+
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const appRootSourceFile: SourceFile = appRoot.sourceFile;
|
|
533
|
+
const appRootSymbol: Symbol = appRoot.symbol;
|
|
534
|
+
const filePath = path.relative(targetDir, appRootSourceFile.getFilePath());
|
|
535
|
+
const appRootFile = project.createSourceFile(
|
|
536
|
+
'__generated__/router/app_root.ts',
|
|
537
|
+
'',
|
|
538
|
+
{overwrite: true},
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
const moduleSpecifier = appRootFile.getRelativePathAsModuleSpecifierTo(
|
|
542
|
+
appRootSourceFile.getFilePath(),
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
appRootFile.addStatements(`/*
|
|
546
|
+
* This file was generated by \`pastoria\`.
|
|
547
|
+
* Do not modify this file directly.
|
|
548
|
+
*/
|
|
549
|
+
|
|
550
|
+
export {${appRootSymbol.getName()} as App} from '${moduleSpecifier}';
|
|
551
|
+
`);
|
|
552
|
+
|
|
553
|
+
await appRootFile.save();
|
|
554
|
+
|
|
555
|
+
console.log(
|
|
556
|
+
'Created app root for',
|
|
557
|
+
pc.green(appRootSymbol.getName()),
|
|
558
|
+
'exported from',
|
|
559
|
+
pc.yellow(filePath),
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async function generateGraphqlContext(
|
|
564
|
+
project: Project,
|
|
565
|
+
metadata: PastoriaMetadata,
|
|
566
|
+
) {
|
|
567
|
+
const targetDir = process.cwd();
|
|
568
|
+
const gqlContext: ExportedSymbol | null = metadata.gqlContext;
|
|
569
|
+
const contextFile = project.createSourceFile(
|
|
570
|
+
'__generated__/router/context.ts',
|
|
571
|
+
'',
|
|
572
|
+
{overwrite: true},
|
|
573
|
+
);
|
|
310
574
|
|
|
311
575
|
if (gqlContext != null) {
|
|
312
576
|
const contextSourceFile: SourceFile = gqlContext.sourceFile;
|
|
@@ -351,95 +615,53 @@ export class Context extends PastoriaRootContext {}
|
|
|
351
615
|
}
|
|
352
616
|
|
|
353
617
|
await contextFile.save();
|
|
618
|
+
}
|
|
354
619
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const moduleSpecifier =
|
|
364
|
-
routerFiles.jsResource.getRelativePathAsModuleSpecifierTo(
|
|
365
|
-
sourceFile.getFilePath(),
|
|
366
|
-
);
|
|
620
|
+
async function generateServerHandler(
|
|
621
|
+
project: Project,
|
|
622
|
+
metadata: PastoriaMetadata,
|
|
623
|
+
) {
|
|
624
|
+
if (metadata.serverHandlers.size === 0) {
|
|
625
|
+
await project
|
|
626
|
+
.getSourceFile('__generated__/router/server_handler.ts')
|
|
627
|
+
?.deleteImmediately();
|
|
367
628
|
|
|
368
|
-
|
|
369
|
-
name: `"${resourceName}"`,
|
|
370
|
-
initializer: (writer) => {
|
|
371
|
-
writer.block(() => {
|
|
372
|
-
writer
|
|
373
|
-
.writeLine(`src: "${filePath}",`)
|
|
374
|
-
.writeLine(
|
|
375
|
-
`loader: () => import("${moduleSpecifier}").then(m => m.${symbol.getName()})`,
|
|
376
|
-
);
|
|
377
|
-
});
|
|
378
|
-
},
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
console.log(
|
|
382
|
-
'Created resource',
|
|
383
|
-
pc.cyan(resourceName),
|
|
384
|
-
'for',
|
|
385
|
-
pc.green(symbol.getName()),
|
|
386
|
-
'exported from',
|
|
387
|
-
pc.yellow(filePath),
|
|
388
|
-
);
|
|
629
|
+
return;
|
|
389
630
|
}
|
|
390
631
|
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
.getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
395
|
-
|
|
396
|
-
routerConf.getPropertyOrThrow('noop').remove();
|
|
632
|
+
const sourceText = `import express from 'express';
|
|
633
|
+
export const router = express.Router();
|
|
634
|
+
`;
|
|
397
635
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
636
|
+
const serverHandlerTemplate = project.createSourceFile(
|
|
637
|
+
'__generated__/router/server_handler.ts',
|
|
638
|
+
sourceText,
|
|
639
|
+
{overwrite: true},
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
let serverHandlerImportIndex = 0;
|
|
643
|
+
for (const [
|
|
644
|
+
routeName,
|
|
645
|
+
{symbol, sourceFile},
|
|
646
|
+
] of metadata.serverHandlers.entries()) {
|
|
647
|
+
const importAlias = `e${serverHandlerImportIndex++}`;
|
|
401
648
|
const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
|
|
402
649
|
const moduleSpecifier =
|
|
403
|
-
|
|
650
|
+
serverHandlerTemplate.getRelativePathAsModuleSpecifierTo(
|
|
404
651
|
sourceFile.getFilePath(),
|
|
405
652
|
);
|
|
406
653
|
|
|
407
|
-
|
|
654
|
+
serverHandlerTemplate.addImportDeclaration({
|
|
408
655
|
moduleSpecifier,
|
|
409
|
-
namedImports: [
|
|
410
|
-
{
|
|
411
|
-
name: symbol.getName(),
|
|
412
|
-
alias: importAlias,
|
|
413
|
-
},
|
|
414
|
-
],
|
|
656
|
+
namedImports: [{name: symbol.getName(), alias: importAlias}],
|
|
415
657
|
});
|
|
416
658
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
writer
|
|
421
|
-
.write('{')
|
|
422
|
-
.indent(() => {
|
|
423
|
-
writer.writeLine(`entrypoint: ${importAlias},`);
|
|
424
|
-
if (params.size === 0) {
|
|
425
|
-
writer.writeLine(`schema: z.object({})`);
|
|
426
|
-
} else {
|
|
427
|
-
writer.writeLine(`schema: z.object({`);
|
|
428
|
-
for (const [paramName, paramType] of Array.from(params)) {
|
|
429
|
-
writer.writeLine(
|
|
430
|
-
` ${paramName}: ${zodSchemaOfType(tc, paramType)},`,
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
writer.writeLine('})');
|
|
435
|
-
}
|
|
436
|
-
})
|
|
437
|
-
.write('} as const');
|
|
438
|
-
},
|
|
439
|
-
});
|
|
659
|
+
serverHandlerTemplate.addStatements(
|
|
660
|
+
`router.use('${routeName}', ${importAlias})`,
|
|
661
|
+
);
|
|
440
662
|
|
|
441
663
|
console.log(
|
|
442
|
-
'Created
|
|
664
|
+
'Created server handler',
|
|
443
665
|
pc.cyan(routeName),
|
|
444
666
|
'for',
|
|
445
667
|
pc.green(symbol.getName()),
|
|
@@ -448,5 +670,23 @@ export class Context extends PastoriaRootContext {}
|
|
|
448
670
|
);
|
|
449
671
|
}
|
|
450
672
|
|
|
451
|
-
await
|
|
673
|
+
await serverHandlerTemplate.save();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export async function generatePastoriaArtifacts() {
|
|
677
|
+
const targetDir = process.cwd();
|
|
678
|
+
const project = new Project({
|
|
679
|
+
tsConfigFilePath: path.join(targetDir, 'tsconfig.json'),
|
|
680
|
+
manipulationSettings: {
|
|
681
|
+
indentationText: IndentationText.TwoSpaces,
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
const metadata = collectPastoriaMetadata(project);
|
|
686
|
+
|
|
687
|
+
await generateAppRoot(project, metadata);
|
|
688
|
+
await generateGraphqlContext(project, metadata);
|
|
689
|
+
await generateRouter(project, metadata);
|
|
690
|
+
await generateJsResource(project, metadata);
|
|
691
|
+
await generateServerHandler(project, metadata);
|
|
452
692
|
}
|