@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.
- package/CHANGELOG.md +155 -0
- package/dist/config.d.cts +2 -2
- package/dist/config.d.mts +2 -2
- package/dist/{index-Dx9Kk5bR.d.mts → index-Bw548Bta.d.mts} +2 -2
- package/dist/{index-Dx9Kk5bR.d.mts.map → index-Bw548Bta.d.mts.map} +1 -1
- package/dist/{index-Dz2a7xQU.d.cts → index-DTpkeFb8.d.cts} +2 -2
- package/dist/{index-Dz2a7xQU.d.cts.map → index-DTpkeFb8.d.cts.map} +1 -1
- package/dist/index.cjs +283 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +283 -31
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CG1LBk7s.cjs → openapi-BzNOC7_i.cjs} +14 -2
- package/dist/openapi-BzNOC7_i.cjs.map +1 -0
- package/dist/{openapi-CtRVU6RD.mjs → openapi-Cfwi0fqF.mjs} +14 -2
- package/dist/openapi-Cfwi0fqF.mjs.map +1 -0
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{types-CqfhdmRi.d.mts → types-B3GQUg3k.d.mts} +3 -1
- package/dist/{types-CqfhdmRi.d.mts.map → types-B3GQUg3k.d.mts.map} +1 -1
- package/dist/{types-DdHfUbxk.d.cts → types-BWQDiooe.d.cts} +3 -1
- package/dist/{types-DdHfUbxk.d.cts.map → types-BWQDiooe.d.cts.map} +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +8 -7
- package/src/build/__tests__/index-new.spec.ts +3 -1
- package/src/build/__tests__/manifests.spec.ts +2 -2
- package/src/build/index.ts +61 -21
- package/src/build/manifests.ts +62 -5
- package/src/dev/index.ts +25 -11
- package/src/generators/EndpointGenerator.ts +14 -2
- package/src/generators/QueueGenerator.ts +245 -0
- package/src/generators/SubscriberGenerator.ts +5 -0
- package/src/generators/TopicGenerator.ts +45 -0
- package/src/generators/__tests__/QueueGenerator.spec.ts +141 -0
- package/src/generators/index.ts +2 -0
- package/src/init/generators/test.ts +2 -1
- package/src/init/versions.ts +9 -9
- package/src/types.ts +14 -51
- package/dist/openapi-CG1LBk7s.cjs.map +0 -1
- 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
|
+
});
|
package/src/generators/index.ts
CHANGED
|
@@ -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 {
|
|
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() {
|
package/src/init/versions.ts
CHANGED
|
@@ -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': '~
|
|
34
|
-
'@geekmidas/cloud': '~1.0
|
|
35
|
-
'@geekmidas/constructs': '~
|
|
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
|
|
38
|
+
'@geekmidas/envkit': '~1.1.0',
|
|
39
39
|
'@geekmidas/errors': '~1.0.1',
|
|
40
|
-
'@geekmidas/events': '~1.1.
|
|
40
|
+
'@geekmidas/events': '~1.1.5',
|
|
41
41
|
'@geekmidas/logger': '~1.0.2',
|
|
42
|
-
'@geekmidas/rate-limit': '~
|
|
43
|
-
'@geekmidas/schema': '~1.0.
|
|
44
|
-
'@geekmidas/services': '~
|
|
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': '~
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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';
|