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/build.ts CHANGED
@@ -1,30 +1,32 @@
1
1
  import ParcelWatcher, {getEventsSince, writeSnapshot} from '@parcel/watcher';
2
- import tailwindcss from '@tailwindcss/vite';
3
- import react from '@vitejs/plugin-react';
4
2
  import {spawn} from 'node:child_process';
5
3
  import {access, readFile} from 'node:fs/promises';
6
4
  import path from 'node:path';
7
5
  import {IndentationText, Project} from 'ts-morph';
8
- import {
9
- build,
10
- InlineConfig,
11
- PluginOption,
12
- type BuildEnvironmentOptions,
13
- type Plugin,
14
- } from 'vite';
15
- import {cjsInterop} from 'vite-plugin-cjs-interop';
6
+ import {build} from 'vite';
16
7
  import {
17
8
  generatePastoriaArtifacts,
18
9
  generatePastoriaExports,
19
10
  PASTORIA_TAG_REGEX,
20
11
  PastoriaMetadata,
21
12
  } from './generate.js';
22
-
23
- interface PastoriaCapabilities {
24
- hasAppRoot: boolean;
25
- hasServerHandler: boolean;
13
+ import {logger, logInfo} from './logger.js';
14
+ import {CLIENT_BUILD, createBuildConfig, SERVER_BUILD} from './vite_plugin.js';
15
+
16
+ enum PastoriaMakePhase {
17
+ PASTORIA_EXPORTS,
18
+ PASTORIA_ARTIFACTS,
19
+ RELAY,
20
+ GRATS,
26
21
  }
27
22
 
23
+ const ALL_MAKE_PHASES = new Set([
24
+ PastoriaMakePhase.PASTORIA_EXPORTS,
25
+ PastoriaMakePhase.PASTORIA_ARTIFACTS,
26
+ PastoriaMakePhase.RELAY,
27
+ PastoriaMakePhase.GRATS,
28
+ ]);
29
+
28
30
  const SNAPSHOT_PATH = '.pastoriainfo';
29
31
 
30
32
  async function runCommand(command: string, args: string[]): Promise<void> {
@@ -90,188 +92,109 @@ function fileMatchesRelayImports(filePath: string, content: string): boolean {
90
92
  );
91
93
  }
92
94
 
93
- async function analyzeChangedFiles(
95
+ async function requiredMakePhasesForChanges(
94
96
  events: Array<{type: string; path: string}>,
95
- ): Promise<{
96
- needsPastoriaExports: boolean;
97
- needsGratsCompiler: boolean;
98
- needsRelayCompiler: boolean;
99
- needsPastoriaArtifacts: boolean;
100
- }> {
101
- let needsPastoriaExports = false;
102
- let needsGratsCompiler = false;
103
- let needsRelayCompiler = false;
104
- let needsPastoriaArtifacts = false;
105
-
106
- for (const event of events) {
107
- const filePath = event.path;
108
-
109
- // Skip non-TypeScript/TSX files
110
- if (!filePath.match(/\.(ts|tsx)$/)) {
111
- continue;
112
- }
113
-
114
- // For delete events, we can't read content, so assume it might affect all pipelines
115
- if (event.type === 'delete') {
116
- needsPastoriaExports = true;
117
- needsGratsCompiler = true;
118
- needsRelayCompiler = true;
119
- needsPastoriaArtifacts = true;
120
- continue;
121
- }
97
+ ): Promise<Set<PastoriaMakePhase>> {
98
+ let makePhases = new Set<PastoriaMakePhase>();
122
99
 
123
- // Read file content for create/update events
124
- try {
125
- const content = await readFile(filePath, 'utf-8');
100
+ await Promise.all(
101
+ events.map(async (event) => {
102
+ const filePath = event.path;
126
103
 
127
- if (fileMatchesPastoriaTags(filePath, content)) {
128
- needsPastoriaExports = true;
129
- needsPastoriaArtifacts = true;
104
+ // Skip non-TypeScript/TSX files
105
+ if (!filePath.match(/\.(ts|tsx)$/)) {
106
+ return;
130
107
  }
131
108
 
132
- if (fileMatchesGratsTags(filePath, content)) {
133
- needsGratsCompiler = true;
134
- needsRelayCompiler = true; // Relay depends on Grats schema
109
+ // For delete events, we can't read content, so assume it might affect all pipelines
110
+ if (event.type === 'delete') {
111
+ makePhases = ALL_MAKE_PHASES;
112
+ return;
135
113
  }
136
114
 
137
- if (fileMatchesRelayImports(filePath, content)) {
138
- needsRelayCompiler = true;
139
- }
140
- } catch {
141
- // If we can't read the file, assume it might affect all pipelines
142
- needsPastoriaExports = true;
143
- needsGratsCompiler = true;
144
- needsRelayCompiler = true;
145
- needsPastoriaArtifacts = true;
146
- }
147
- }
115
+ // Read file content for create/update events
116
+ try {
117
+ const content = await readFile(filePath, 'utf-8');
148
118
 
149
- return {
150
- needsPastoriaExports,
151
- needsGratsCompiler,
152
- needsRelayCompiler,
153
- needsPastoriaArtifacts,
154
- };
155
- }
119
+ if (fileMatchesPastoriaTags(filePath, content)) {
120
+ makePhases.add(PastoriaMakePhase.PASTORIA_EXPORTS);
121
+ makePhases.add(PastoriaMakePhase.PASTORIA_ARTIFACTS);
122
+ }
156
123
 
157
- function generateClientEntry({hasAppRoot}: PastoriaCapabilities): string {
158
- const appImport = hasAppRoot
159
- ? `import {App} from '#genfiles/router/app_root';`
160
- : '';
161
- const appValue = hasAppRoot ? 'App' : 'null';
124
+ if (fileMatchesGratsTags(filePath, content)) {
125
+ makePhases.add(PastoriaMakePhase.GRATS);
126
+ makePhases.add(PastoriaMakePhase.RELAY); // Relay depends on Grats schema
127
+ }
162
128
 
163
- return `// Generated by Pastoria.
164
- import {createRouterApp} from '#genfiles/router/router';
165
- ${appImport}
166
- import {hydrateRoot} from 'react-dom/client';
129
+ if (fileMatchesRelayImports(filePath, content)) {
130
+ makePhases.add(PastoriaMakePhase.RELAY);
131
+ }
132
+ } catch {
133
+ // If we can't read the file, assume it might affect all pipelines
134
+ makePhases = ALL_MAKE_PHASES;
135
+ }
136
+ }),
137
+ );
167
138
 
168
- async function main() {
169
- const RouterApp = await createRouterApp();
170
- hydrateRoot(document, <RouterApp App={${appValue}} />);
139
+ return makePhases;
171
140
  }
172
141
 
173
- main();
174
- `;
175
- }
142
+ function requiredMakePhasesForArgs(steps: string[]): Set<PastoriaMakePhase> {
143
+ const validSteps = new Set(['schema', 'relay', 'router']);
144
+ const needs = new Set<PastoriaMakePhase>();
176
145
 
177
- function generateServerEntry({
178
- hasAppRoot,
179
- hasServerHandler,
180
- }: PastoriaCapabilities): string {
181
- const appImport = hasAppRoot
182
- ? `import {App} from '#genfiles/router/app_root';`
183
- : '';
184
- const appValue = hasAppRoot ? 'App' : 'null';
185
-
186
- const serverHandlerImport = hasServerHandler
187
- ? `import {router as serverHandler} from '#genfiles/router/server_handler';`
188
- : '';
189
- const serverHandlerUse = hasServerHandler
190
- ? ' router.use(serverHandler)'
191
- : '';
192
-
193
- return `// Generated by Pastoria.
194
- import {JSResource} from '#genfiles/router/js_resource';
195
- import {
196
- listRoutes,
197
- router__createAppFromEntryPoint,
198
- router__loadEntryPoint,
199
- } from '#genfiles/router/router';
200
- import {getSchema} from '#genfiles/schema/schema';
201
- import {Context} from '#genfiles/router/context';
202
- ${appImport}
203
- ${serverHandlerImport}
204
- import express from 'express';
205
- import {GraphQLSchema, specifiedDirectives} from 'graphql';
206
- import {PastoriaConfig} from 'pastoria-config';
207
- import {createRouterHandler} from 'pastoria-runtime/server';
208
- import type {Manifest} from 'vite';
209
-
210
- const schemaConfig = getSchema().toConfig();
211
- const schema = new GraphQLSchema({
212
- ...schemaConfig,
213
- directives: [...specifiedDirectives, ...schemaConfig.directives],
214
- });
215
-
216
- export function createHandler(
217
- persistedQueries: Record<string, string>,
218
- config: Required<PastoriaConfig>,
219
- manifest?: Manifest,
220
- ) {
221
- const routeHandler = createRouterHandler(
222
- listRoutes(),
223
- JSResource.srcOfModuleId,
224
- router__loadEntryPoint,
225
- router__createAppFromEntryPoint,
226
- ${appValue},
227
- schema,
228
- (req) => Context.createFromRequest(req),
229
- persistedQueries,
230
- config,
231
- manifest,
232
- );
146
+ for (const step of steps) {
147
+ if (!validSteps.has(step)) {
148
+ throw new Error(
149
+ `Invalid build step: ${step}. Valid steps are: schema, relay, router`,
150
+ );
151
+ }
233
152
 
234
- const router = express.Router();
235
- router.use(routeHandler);
236
- ${serverHandlerUse}
153
+ switch (step) {
154
+ case 'schema':
155
+ needs.add(PastoriaMakePhase.GRATS);
156
+ break;
157
+ case 'relay':
158
+ needs.add(PastoriaMakePhase.RELAY);
237
159
 
238
- return router;
239
- }
240
- `;
160
+ break;
161
+ case 'router':
162
+ needs.add(PastoriaMakePhase.PASTORIA_EXPORTS);
163
+ needs.add(PastoriaMakePhase.PASTORIA_ARTIFACTS);
164
+ break;
165
+ }
166
+ }
167
+
168
+ return needs;
241
169
  }
242
170
 
243
171
  async function executeBuildSteps(
244
172
  project: Project,
245
- needs: {
246
- needsPastoriaExports: boolean;
247
- needsGratsCompiler: boolean;
248
- needsRelayCompiler: boolean;
249
- needsPastoriaArtifacts: boolean;
250
- },
173
+ needs: Set<PastoriaMakePhase>,
251
174
  ): Promise<boolean> {
252
175
  let rebuiltAnything = false;
253
176
  let cachedMetadata: PastoriaMetadata | undefined = undefined;
254
177
 
255
- if (needs.needsPastoriaExports) {
256
- console.log('Running Pastoria exports generation...');
178
+ if (needs.has(PastoriaMakePhase.PASTORIA_EXPORTS)) {
179
+ logInfo('Running Pastoria exports generation...');
257
180
  cachedMetadata = await generatePastoriaExports(project);
258
181
  rebuiltAnything = true;
259
182
  }
260
183
 
261
- if (needs.needsGratsCompiler) {
262
- console.log('Running Grats compiler...');
184
+ if (needs.has(PastoriaMakePhase.GRATS)) {
185
+ logInfo('Running Grats compiler...');
263
186
  await runGratsCompiler();
264
187
  rebuiltAnything = true;
265
188
  }
266
189
 
267
- if (needs.needsRelayCompiler) {
268
- console.log('Running Relay compiler...');
190
+ if (needs.has(PastoriaMakePhase.RELAY)) {
191
+ logInfo('Running Relay compiler...');
269
192
  await runRelayCompiler();
270
193
  rebuiltAnything = true;
271
194
  }
272
195
 
273
- if (needs.needsPastoriaArtifacts) {
274
- console.log('Running Pastoria artifacts generation...');
196
+ if (needs.has(PastoriaMakePhase.PASTORIA_ARTIFACTS)) {
197
+ logInfo('Running Pastoria artifacts generation...');
275
198
  await generatePastoriaArtifacts(project, cachedMetadata);
276
199
  rebuiltAnything = true;
277
200
  }
@@ -279,148 +202,6 @@ async function executeBuildSteps(
279
202
  return rebuiltAnything;
280
203
  }
281
204
 
282
- async function determineCapabilities(): Promise<PastoriaCapabilities> {
283
- const capabilities: PastoriaCapabilities = {
284
- hasAppRoot: false,
285
- hasServerHandler: false,
286
- };
287
-
288
- async function hasAppRoot() {
289
- try {
290
- await access('__generated__/router/app_root.ts');
291
- capabilities.hasAppRoot = true;
292
- } catch {}
293
- }
294
-
295
- async function hasServerHandler() {
296
- try {
297
- await access('__generated__/router/server_handler.ts');
298
- capabilities.hasServerHandler = true;
299
- } catch {}
300
- }
301
-
302
- await Promise.all([hasAppRoot(), hasServerHandler()]);
303
- return capabilities;
304
- }
305
-
306
- function pastoriaEntryPlugin(): Plugin {
307
- const clientEntryModuleId = 'virtual:pastoria-entry-client.tsx';
308
- const serverEntryModuleId = 'virtual:pastoria-entry-server.tsx';
309
-
310
- return {
311
- name: 'pastoria-entry',
312
- resolveId(id) {
313
- if (id === clientEntryModuleId) {
314
- return clientEntryModuleId; // Return without \0 prefix so React plugin can see .tsx extension
315
- } else if (id === serverEntryModuleId) {
316
- return serverEntryModuleId;
317
- }
318
- },
319
- async load(id) {
320
- const capabilities = await determineCapabilities();
321
- if (id === clientEntryModuleId) {
322
- return generateClientEntry(capabilities);
323
- } else if (id === serverEntryModuleId) {
324
- return generateServerEntry(capabilities);
325
- }
326
- },
327
- };
328
- }
329
-
330
- export const CLIENT_BUILD: BuildEnvironmentOptions = {
331
- outDir: 'dist/client',
332
- rollupOptions: {
333
- input: 'virtual:pastoria-entry-client.tsx',
334
- },
335
- };
336
-
337
- export const SERVER_BUILD: BuildEnvironmentOptions = {
338
- outDir: 'dist/server',
339
- ssr: true,
340
- rollupOptions: {
341
- input: 'virtual:pastoria-entry-server.tsx',
342
- },
343
- };
344
-
345
- export function createBuildConfig(
346
- buildEnv: BuildEnvironmentOptions,
347
- ): InlineConfig {
348
- return {
349
- appType: 'custom' as const,
350
- build: {
351
- ...buildEnv,
352
- assetsInlineLimit: 0,
353
- manifest: true,
354
- ssrManifest: true,
355
- },
356
- plugins: [
357
- pastoriaEntryPlugin(),
358
- tailwindcss() as PluginOption,
359
- react({
360
- babel: {
361
- plugins: [['babel-plugin-react-compiler', {}], 'relay'],
362
- },
363
- }),
364
- cjsInterop({
365
- dependencies: ['react-relay', 'react-relay/hooks', 'relay-runtime'],
366
- }),
367
- ],
368
- ssr: {
369
- noExternal: ['pastoria-runtime'],
370
- },
371
- };
372
- }
373
-
374
- async function createReleaseBuild() {
375
- await build({
376
- ...createBuildConfig(CLIENT_BUILD),
377
- configFile: false,
378
- });
379
-
380
- await build({
381
- ...createBuildConfig(SERVER_BUILD),
382
- configFile: false,
383
- });
384
- }
385
-
386
- function determineBuildStepsFromArgs(steps: string[]): {
387
- needsPastoriaExports: boolean;
388
- needsGratsCompiler: boolean;
389
- needsRelayCompiler: boolean;
390
- needsPastoriaArtifacts: boolean;
391
- } {
392
- const validSteps = new Set(['schema', 'relay', 'router']);
393
- const needs = {
394
- needsPastoriaExports: false,
395
- needsGratsCompiler: false,
396
- needsRelayCompiler: false,
397
- needsPastoriaArtifacts: false,
398
- };
399
-
400
- for (const step of steps) {
401
- if (!validSteps.has(step)) {
402
- throw new Error(
403
- `Invalid build step: ${step}. Valid steps are: schema, relay, router`,
404
- );
405
- }
406
-
407
- switch (step) {
408
- case 'schema':
409
- needs.needsGratsCompiler = true;
410
- break;
411
- case 'relay':
412
- needs.needsRelayCompiler = true;
413
- break;
414
- case 'router':
415
- needs.needsPastoriaExports = true;
416
- needs.needsPastoriaArtifacts = true;
417
- break;
418
- }
419
- }
420
-
421
- return needs;
422
- }
423
-
424
205
  export async function createBuild(
425
206
  steps: string[],
426
207
  opts: {
@@ -443,21 +224,16 @@ export async function createBuild(
443
224
  });
444
225
 
445
226
  const cwd = process.cwd();
446
- let needsPastoriaExports = opts.alwaysMake;
447
- let needsGratsCompiler = opts.alwaysMake;
448
- let needsRelayCompiler = opts.alwaysMake;
449
- let needsPastoriaArtifacts = opts.alwaysMake;
227
+ let makePhases = new Set<PastoriaMakePhase>();
450
228
 
451
229
  // If specific steps are provided, override automatic inference
452
230
  if (steps.length > 0) {
453
- const stepsNeeds = determineBuildStepsFromArgs(steps);
454
- needsPastoriaExports = stepsNeeds.needsPastoriaExports;
455
- needsGratsCompiler = stepsNeeds.needsGratsCompiler;
456
- needsRelayCompiler = stepsNeeds.needsRelayCompiler;
457
- needsPastoriaArtifacts = stepsNeeds.needsPastoriaArtifacts;
231
+ makePhases = makePhases.union(requiredMakePhasesForArgs(steps));
232
+ } else if (opts.alwaysMake) {
233
+ makePhases = ALL_MAKE_PHASES;
458
234
  }
459
235
  // Use @parcel/watcher to get changes since last snapshot
460
- else if (!opts.alwaysMake) {
236
+ else {
461
237
  try {
462
238
  // Check if snapshot exists - if not, do a full build
463
239
  await access(SNAPSHOT_PATH);
@@ -467,59 +243,56 @@ export async function createBuild(
467
243
 
468
244
  if (events.length > 0) {
469
245
  // Analyze which files changed and determine what needs to be rebuilt
470
- const analysis = await analyzeChangedFiles(events);
471
- needsPastoriaExports = analysis.needsPastoriaExports;
472
- needsGratsCompiler = analysis.needsGratsCompiler;
473
- needsRelayCompiler =
474
- analysis.needsRelayCompiler || analysis.needsGratsCompiler;
475
- needsPastoriaArtifacts =
476
- analysis.needsPastoriaArtifacts || analysis.needsPastoriaExports;
246
+ makePhases = makePhases.union(
247
+ await requiredMakePhasesForChanges(events),
248
+ );
477
249
  }
478
250
  } catch (err) {
479
251
  // No snapshot exists yet, or error reading it - do a full build
480
- needsPastoriaExports = true;
481
- needsGratsCompiler = true;
482
- needsRelayCompiler = true;
483
- needsPastoriaArtifacts = true;
252
+ makePhases = ALL_MAKE_PHASES;
484
253
  }
485
254
  }
486
255
 
487
256
  // Execute build pipeline conditionally
488
- await executeBuildSteps(project, {
489
- needsPastoriaExports,
490
- needsGratsCompiler,
491
- needsRelayCompiler,
492
- needsPastoriaArtifacts,
493
- });
257
+ await executeBuildSteps(project, makePhases);
494
258
 
495
259
  // Write snapshot for next incremental build
496
260
  await writeSnapshot(cwd, SNAPSHOT_PATH);
497
261
 
498
262
  if (opts.release) {
499
- await createReleaseBuild();
263
+ await build({
264
+ ...createBuildConfig(CLIENT_BUILD),
265
+ configFile: false,
266
+ });
267
+
268
+ await build({
269
+ ...createBuildConfig(SERVER_BUILD),
270
+ configFile: false,
271
+ });
500
272
  }
501
273
 
502
274
  // Start watch mode if requested
503
275
  if (opts.watch) {
504
- console.log('Watching for changes...');
276
+ logInfo('Watching for changes...');
505
277
 
506
278
  const subscription = await ParcelWatcher.subscribe(
507
279
  cwd,
508
280
  async (err, events) => {
509
281
  if (err) {
510
- console.error('Watch error:', err);
282
+ logger.error('Watch error!', {error: err});
511
283
  return;
512
284
  }
513
285
 
514
286
  // Analyze which files changed and determine what needs to be rebuilt
515
- const analysis = await analyzeChangedFiles(events);
516
-
517
- const rebuiltAnything = await executeBuildSteps(project, analysis);
287
+ const rebuiltAnything = await executeBuildSteps(
288
+ project,
289
+ await requiredMakePhasesForChanges(events),
290
+ );
518
291
 
519
292
  if (rebuiltAnything) {
520
293
  // Write snapshot after successful rebuild
521
294
  await writeSnapshot(cwd, SNAPSHOT_PATH);
522
- console.log('Rebuild complete. Watching for changes...');
295
+ logInfo('Rebuild complete. Watching for changes...');
523
296
  }
524
297
  },
525
298
  );
package/src/devserver.ts CHANGED
@@ -2,10 +2,11 @@ import cookieParser from 'cookie-parser';
2
2
  import dotenv from 'dotenv';
3
3
  import express from 'express';
4
4
  import {readFile} from 'node:fs/promises';
5
- import pc from 'picocolors';
6
5
  import {loadConfig, PastoriaConfig} from 'pastoria-config';
6
+ import pc from 'picocolors';
7
7
  import {createServer as createViteServer, type Manifest} from 'vite';
8
- import {CLIENT_BUILD, createBuildConfig} from './build.js';
8
+ import {logInfo} from './logger.js';
9
+ import {CLIENT_BUILD, createBuildConfig} from './vite_plugin.js';
9
10
 
10
11
  interface PersistedQueries {
11
12
  [hash: string]: string;
@@ -51,7 +52,7 @@ export async function startDevserver(opts: {port: string}) {
51
52
  if (err) {
52
53
  console.error(err);
53
54
  } else {
54
- console.log(pc.cyan(`Listening on port ${opts.port}!`));
55
+ logInfo(pc.cyan(`Listening on port ${opts.port}!`));
55
56
  }
56
57
  });
57
58
  }