@geekmidas/cli 1.11.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/dist/config.d.cts +2 -2
  3. package/dist/config.d.mts +2 -2
  4. package/dist/{index-Dx9Kk5bR.d.mts → index-Bw548Bta.d.mts} +2 -2
  5. package/dist/{index-Dx9Kk5bR.d.mts.map → index-Bw548Bta.d.mts.map} +1 -1
  6. package/dist/{index-Dz2a7xQU.d.cts → index-DTpkeFb8.d.cts} +2 -2
  7. package/dist/{index-Dz2a7xQU.d.cts.map → index-DTpkeFb8.d.cts.map} +1 -1
  8. package/dist/index.cjs +283 -31
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.mjs +283 -31
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{openapi-CG1LBk7s.cjs → openapi-BzNOC7_i.cjs} +14 -2
  13. package/dist/openapi-BzNOC7_i.cjs.map +1 -0
  14. package/dist/{openapi-CtRVU6RD.mjs → openapi-Cfwi0fqF.mjs} +14 -2
  15. package/dist/openapi-Cfwi0fqF.mjs.map +1 -0
  16. package/dist/openapi.cjs +1 -1
  17. package/dist/openapi.d.cts +1 -1
  18. package/dist/openapi.d.mts +1 -1
  19. package/dist/openapi.mjs +1 -1
  20. package/dist/{types-CqfhdmRi.d.mts → types-B3GQUg3k.d.mts} +3 -1
  21. package/dist/{types-CqfhdmRi.d.mts.map → types-B3GQUg3k.d.mts.map} +1 -1
  22. package/dist/{types-DdHfUbxk.d.cts → types-BWQDiooe.d.cts} +3 -1
  23. package/dist/{types-DdHfUbxk.d.cts.map → types-BWQDiooe.d.cts.map} +1 -1
  24. package/dist/workspace/index.d.cts +2 -2
  25. package/dist/workspace/index.d.mts +2 -2
  26. package/package.json +8 -7
  27. package/src/build/__tests__/index-new.spec.ts +3 -1
  28. package/src/build/__tests__/manifests.spec.ts +2 -2
  29. package/src/build/index.ts +61 -21
  30. package/src/build/manifests.ts +62 -5
  31. package/src/dev/index.ts +25 -11
  32. package/src/generators/EndpointGenerator.ts +14 -2
  33. package/src/generators/QueueGenerator.ts +245 -0
  34. package/src/generators/SubscriberGenerator.ts +5 -0
  35. package/src/generators/TopicGenerator.ts +45 -0
  36. package/src/generators/__tests__/QueueGenerator.spec.ts +141 -0
  37. package/src/generators/index.ts +2 -0
  38. package/src/init/generators/test.ts +2 -1
  39. package/src/init/versions.ts +9 -9
  40. package/src/types.ts +14 -51
  41. package/dist/openapi-CG1LBk7s.cjs.map +0 -1
  42. package/dist/openapi-CtRVU6RD.mjs.map +0 -1
@@ -0,0 +1,45 @@
1
+ import { Topic } from '@geekmidas/constructs/topic';
2
+ import type { BuildContext } from '../build/types';
3
+ import type { TopicInfo } from '../types';
4
+ import {
5
+ ConstructGenerator,
6
+ type GeneratedConstruct,
7
+ type GeneratorOptions,
8
+ } from './Generator';
9
+
10
+ /**
11
+ * Discovers `t` topics into `manifest.topics`. A topic is a *resource*, not a
12
+ * function — it has no handler to generate. Producers reach it via its derived
13
+ * publisher (`topic.publisher`, whose connection string is sniffed onto the
14
+ * producing construct) and subscribers bind via `s.topic(topic)`; this generator
15
+ * only records the topic + its event contract so infra can provision the SNS
16
+ * topic. Identical output for every provider (locally pg-boss routes by event
17
+ * name, so there's nothing to run).
18
+ */
19
+ export class TopicGenerator extends ConstructGenerator<
20
+ Topic<any, any>,
21
+ TopicInfo[]
22
+ > {
23
+ isConstruct(value: any): value is Topic<any, any> {
24
+ return Topic.isTopic(value);
25
+ }
26
+
27
+ async build(
28
+ _context: BuildContext,
29
+ constructs: GeneratedConstruct<Topic<any, any>>[],
30
+ _outputDir: string,
31
+ _options?: GeneratorOptions,
32
+ ): Promise<TopicInfo[]> {
33
+ const logger = console;
34
+ const topicInfos = constructs.map(({ construct }) => ({
35
+ name: construct.name,
36
+ events: construct.eventTypes,
37
+ }));
38
+
39
+ for (const topic of topicInfos) {
40
+ logger.log(`Discovered topic: ${topic.name}`);
41
+ }
42
+
43
+ return topicInfos;
44
+ }
45
+ }
@@ -0,0 +1,141 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { type Queue, QueueBuilder } from '@geekmidas/constructs/queue';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { z } from 'zod';
6
+ import {
7
+ cleanupDir,
8
+ createMockBuildContext,
9
+ createTempDir,
10
+ } from '../../__tests__/test-helpers';
11
+ import type { GeneratedConstruct } from '../Generator';
12
+ import { QueueGenerator } from '../QueueGenerator';
13
+
14
+ const schema = z.object({ orderId: z.string() });
15
+
16
+ describe('QueueGenerator', () => {
17
+ let tempDir: string;
18
+ let outputDir: string;
19
+ let generator: QueueGenerator;
20
+ let context: ReturnType<typeof createMockBuildContext>;
21
+
22
+ beforeEach(async () => {
23
+ tempDir = await createTempDir();
24
+ outputDir = join(tempDir, 'output');
25
+ generator = new QueueGenerator();
26
+ context = createMockBuildContext();
27
+ });
28
+
29
+ afterEach(async () => {
30
+ await cleanupDir(tempDir);
31
+ });
32
+
33
+ const createQueueConstruct = (
34
+ key: string,
35
+ name: string,
36
+ ): GeneratedConstruct<Queue<any, any, any, any>> => {
37
+ const queue = new QueueBuilder()
38
+ .queue(name)
39
+ .batchSize(5)
40
+ .message(schema)
41
+ .handle(async () => {});
42
+
43
+ return {
44
+ key,
45
+ name: key.toLowerCase(),
46
+ construct: queue,
47
+ path: {
48
+ absolute: join(tempDir, `${key}.ts`),
49
+ relative: `${key}.ts`,
50
+ },
51
+ };
52
+ };
53
+
54
+ describe('isConstruct', () => {
55
+ it('identifies queues and rejects everything else', () => {
56
+ const queue = new QueueBuilder()
57
+ .queue('orders')
58
+ .message(schema)
59
+ .handle(async () => {});
60
+
61
+ expect(generator.isConstruct(queue)).toBe(true);
62
+ expect(generator.isConstruct({})).toBe(false);
63
+ expect(generator.isConstruct(null)).toBe(false);
64
+ });
65
+ });
66
+
67
+ describe('aws-lambda provider', () => {
68
+ it('generates one AWSLambdaQueue handler per queue', async () => {
69
+ const constructs = [
70
+ createQueueConstruct('ordersQueue', 'orders'),
71
+ createQueueConstruct('emailsQueue', 'emails'),
72
+ ];
73
+
74
+ const infos = await generator.build(context, constructs, outputDir, {
75
+ provider: 'aws-lambda',
76
+ });
77
+
78
+ expect(infos).toHaveLength(2);
79
+ expect(infos[0]).toMatchObject({
80
+ name: 'orders',
81
+ handler: expect.stringContaining('queues/ordersQueue.handler'),
82
+ batchSize: 5,
83
+ });
84
+ // Env requirement from the (sniffed) producer flows nowhere here, but the
85
+ // consumer queue's own services would; assert the field exists.
86
+ expect(infos[0].environment).toBeDefined();
87
+
88
+ const handler = await readFile(
89
+ join(outputDir, 'queues', 'ordersQueue.ts'),
90
+ 'utf-8',
91
+ );
92
+ expect(handler).toContain(
93
+ "import { AWSLambdaQueue } from '@geekmidas/constructs/aws'",
94
+ );
95
+ expect(handler).toContain('import { ordersQueue }');
96
+ expect(handler).toContain('new AWSLambdaQueue(envParser, ordersQueue)');
97
+ expect(handler).toContain('export const handler = adapter.handler');
98
+ });
99
+
100
+ it('returns an empty array for no queues', async () => {
101
+ const infos = await generator.build(context, [], outputDir, {
102
+ provider: 'aws-lambda',
103
+ });
104
+ expect(infos).toEqual([]);
105
+ });
106
+ });
107
+
108
+ describe('server provider', () => {
109
+ it('generates queues.ts with the setupQueues poller even when empty', async () => {
110
+ const infos = await generator.build(context, [], outputDir, {
111
+ provider: 'server',
112
+ });
113
+
114
+ expect(infos).toEqual([]);
115
+
116
+ const content = await readFile(join(outputDir, 'queues.ts'), 'utf-8');
117
+ expect(content).toContain('export async function setupQueues');
118
+ expect(content).toContain('const queues = [');
119
+ expect(content).toContain('EVENT_SUBSCRIBER_CONNECTION_STRING');
120
+ });
121
+
122
+ it('subscribes each queue by its name and validates the payload', async () => {
123
+ const constructs = [
124
+ createQueueConstruct('ordersQueue', 'orders'),
125
+ createQueueConstruct('emailsQueue', 'emails'),
126
+ ];
127
+
128
+ await generator.build(context, constructs, outputDir, {
129
+ provider: 'server',
130
+ });
131
+
132
+ const content = await readFile(join(outputDir, 'queues.ts'), 'utf-8');
133
+ expect(content).toContain('import { ordersQueue }');
134
+ expect(content).toContain('import { emailsQueue }');
135
+ // A queue subscribes to a single "type" — its own name.
136
+ expect(content).toContain('eventSubscriber.subscribe([queue.name]');
137
+ expect(content).toContain("queue.messageSchema['~standard'].validate");
138
+ expect(content).toContain('messages: [validation.value]');
139
+ });
140
+ });
141
+ });
@@ -6,4 +6,6 @@ export {
6
6
  type GeneratedConstruct,
7
7
  type GeneratorOptions,
8
8
  } from './Generator';
9
+ export { QueueGenerator } from './QueueGenerator';
9
10
  export { SubscriberGenerator } from './SubscriberGenerator';
11
+ export { TopicGenerator } from './TopicGenerator';
@@ -72,7 +72,8 @@ import path from 'node:path';
72
72
  import { Credentials } from '@geekmidas/envkit/credentials';
73
73
  import { PostgresKyselyMigrator } from '@geekmidas/testkit/kysely';
74
74
  import { runInitScript } from '@geekmidas/testkit/postgres';
75
- import { FileMigrationProvider, Kysely, PostgresDialect } from 'kysely';
75
+ import { Kysely, PostgresDialect } from 'kysely';
76
+ import { FileMigrationProvider } from 'kysely/migration';
76
77
  import pg from 'pg';
77
78
 
78
79
  export default async function globalSetup() {
@@ -30,22 +30,22 @@ export const GEEKMIDAS_VERSIONS = {
30
30
  '@geekmidas/audit': '~2.0.1',
31
31
  '@geekmidas/auth': '~2.0.1',
32
32
  '@geekmidas/cache': '~1.1.1',
33
- '@geekmidas/client': '~6.0.0',
34
- '@geekmidas/cloud': '~1.0.1',
35
- '@geekmidas/constructs': '~4.0.0',
33
+ '@geekmidas/client': '~7.0.0',
34
+ '@geekmidas/cloud': '~1.1.0',
35
+ '@geekmidas/constructs': '~5.0.0',
36
36
  '@geekmidas/db': '~1.0.2',
37
37
  '@geekmidas/emailkit': '~1.0.1',
38
- '@geekmidas/envkit': '~1.0.7',
38
+ '@geekmidas/envkit': '~1.1.0',
39
39
  '@geekmidas/errors': '~1.0.1',
40
- '@geekmidas/events': '~1.1.4',
40
+ '@geekmidas/events': '~1.1.5',
41
41
  '@geekmidas/logger': '~1.0.2',
42
- '@geekmidas/rate-limit': '~3.0.0',
43
- '@geekmidas/schema': '~1.0.2',
44
- '@geekmidas/services': '~1.1.1',
42
+ '@geekmidas/rate-limit': '~4.0.0',
43
+ '@geekmidas/schema': '~1.0.3',
44
+ '@geekmidas/services': '~2.0.0',
45
45
  '@geekmidas/storage': '~2.0.3',
46
46
  '@geekmidas/studio': '~1.0.1',
47
47
  '@geekmidas/telescope': '~1.0.1',
48
- '@geekmidas/testkit': '~2.0.0',
48
+ '@geekmidas/testkit': '~3.0.0',
49
49
  '@geekmidas/cli': CLI_VERSION,
50
50
  } as const;
51
51
 
package/src/types.ts CHANGED
@@ -239,6 +239,8 @@ export interface GkmConfig {
239
239
  functions?: Routes;
240
240
  crons?: Routes;
241
241
  subscribers?: Routes;
242
+ queues?: Routes;
243
+ topics?: Routes;
242
244
  envParser: string;
243
245
  logger: string;
244
246
  providers?: ProvidersConfig;
@@ -347,54 +349,15 @@ export interface BuildResult {
347
349
  masterKey?: string;
348
350
  }
349
351
 
350
- export interface RouteInfo {
351
- path: string;
352
- method: string;
353
- handler: string;
354
- timeout?: number;
355
- memorySize?: number;
356
- environment?: string[];
357
- authorizer: string;
358
- }
359
-
360
- export interface FunctionInfo {
361
- name: string;
362
- handler: string;
363
- timeout?: number;
364
- memorySize?: number;
365
- environment?: string[];
366
- }
367
-
368
- export interface CronInfo {
369
- name: string;
370
- handler: string;
371
- schedule: string;
372
- timeout?: number;
373
- memorySize?: number;
374
- environment?: string[];
375
- }
376
-
377
- export interface SubscriberInfo {
378
- name: string;
379
- handler: string;
380
- subscribedEvents: string[];
381
- timeout?: number;
382
- memorySize?: number;
383
- environment?: string[];
384
- }
385
-
386
- export interface RoutesManifest {
387
- routes: RouteInfo[];
388
- }
389
-
390
- export interface FunctionsManifest {
391
- functions: FunctionInfo[];
392
- }
393
-
394
- export interface CronsManifest {
395
- crons: CronInfo[];
396
- }
397
-
398
- export interface SubscribersManifest {
399
- subscribers: SubscriberInfo[];
400
- }
352
+ // The deployment manifest types are a shared, dependency-free data contract
353
+ // in `@geekmidas/manifest`, re-exported here for back-compat with existing
354
+ // `@geekmidas/cli` imports.
355
+ export type {
356
+ CronInfo,
357
+ FunctionInfo,
358
+ Manifest,
359
+ QueueInfo,
360
+ RouteInfo,
361
+ SubscriberInfo,
362
+ TopicInfo,
363
+ } from '@geekmidas/manifest';